mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 16:43:00 +08:00
Merge branch 'ppy:master' into colour-encoding-2
This commit is contained in:
commit
9994f13031
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -126,12 +126,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
# macOS agents recently have empty NuGet config files, resulting in restore failures,
|
|
||||||
# see https://github.com/actions/virtual-environments/issues/5768
|
|
||||||
# Add the global nuget package source manually for now.
|
|
||||||
- name: Setup NuGet.Config
|
|
||||||
run: echo '<configuration><packageSources><add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /></packageSources></configuration>' > ~/.config/NuGet/NuGet.Config
|
|
||||||
|
|
||||||
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
|
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
|
||||||
# cannot accept .sln(f) files as arguments.
|
# cannot accept .sln(f) files as arguments.
|
||||||
# Build just the main game for now.
|
# Build just the main game for now.
|
||||||
|
60
Gemfile.lock
60
Gemfile.lock
@ -8,20 +8,20 @@ GEM
|
|||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.570.0)
|
aws-partitions (1.601.0)
|
||||||
aws-sdk-core (3.130.0)
|
aws-sdk-core (3.131.2)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.525.0)
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.55.0)
|
aws-sdk-kms (1.57.0)
|
||||||
aws-sdk-core (~> 3, >= 3.127.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.113.0)
|
aws-sdk-s3 (1.114.0)
|
||||||
aws-sdk-core (~> 3, >= 3.127.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.4.0)
|
aws-sigv4 (1.5.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
@ -36,7 +36,7 @@ GEM
|
|||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.2.3)
|
emoji_regex (3.2.3)
|
||||||
excon (0.92.1)
|
excon (0.92.3)
|
||||||
faraday (1.10.0)
|
faraday (1.10.0)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
@ -56,8 +56,8 @@ GEM
|
|||||||
faraday-em_synchrony (1.0.0)
|
faraday-em_synchrony (1.0.0)
|
||||||
faraday-excon (1.1.0)
|
faraday-excon (1.1.0)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.3)
|
faraday-multipart (1.0.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (~> 2)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.1)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
@ -66,7 +66,7 @@ GEM
|
|||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.6)
|
fastimage (2.2.6)
|
||||||
fastlane (2.205.1)
|
fastlane (2.206.2)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
@ -110,9 +110,9 @@ GEM
|
|||||||
souyuz (= 0.11.1)
|
souyuz (= 0.11.1)
|
||||||
fastlane-plugin-xamarin (0.6.3)
|
fastlane-plugin-xamarin (0.6.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.16.0)
|
google-apis-androidpublisher_v3 (0.23.0)
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
google-apis-core (0.4.2)
|
google-apis-core (0.6.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@ -121,19 +121,19 @@ GEM
|
|||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.10.0)
|
google-apis-iamcredentials_v1 (0.12.0)
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
google-apis-playcustomapp_v1 (0.7.0)
|
google-apis-playcustomapp_v1 (0.9.0)
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
google-apis-storage_v1 (0.11.0)
|
google-apis-storage_v1 (0.16.0)
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.2.0)
|
google-cloud-errors (1.2.0)
|
||||||
google-cloud-storage (1.36.1)
|
google-cloud-storage (1.36.2)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
@ -141,7 +141,7 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.1.2)
|
googleauth (1.2.0)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@ -149,12 +149,12 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (2.0.3)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.4)
|
http-cookie (1.0.5)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.6.1)
|
jmespath (1.6.1)
|
||||||
json (2.6.1)
|
json (2.6.2)
|
||||||
jwt (2.3.0)
|
jwt (2.4.1)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
@ -169,10 +169,10 @@ GEM
|
|||||||
optparse (0.1.1)
|
optparse (0.1.1)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.6.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.7)
|
||||||
racc (1.6.0)
|
racc (1.6.0)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
representable (3.1.1)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
@ -182,9 +182,9 @@ GEM
|
|||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.16.1)
|
signet (0.17.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.0)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
@ -205,11 +205,11 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.1)
|
unf_ext (0.0.8.2)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
webrick (1.7.0)
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.21.0)
|
xcodeproj (1.22.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.623.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.624.1" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.715.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Tournament;
|
using osu.Game.Tournament;
|
||||||
|
using SDL2;
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
@ -29,7 +30,21 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
// run Squirrel first, as the app may exit after these run
|
// run Squirrel first, as the app may exit after these run
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
var windowsVersion = Environment.OSVersion.Version;
|
||||||
|
|
||||||
|
// While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||||
|
// See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms
|
||||||
|
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
||||||
|
{
|
||||||
|
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
||||||
|
"Your operating system is too old to run osu!",
|
||||||
|
"This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setupSquirrel();
|
setupSquirrel();
|
||||||
|
}
|
||||||
|
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
string cwd = Environment.CurrentDirectory;
|
string cwd = Environment.CurrentDirectory;
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.40" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -11,7 +9,7 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkMod : BenchmarkTest
|
public class BenchmarkMod : BenchmarkTest
|
||||||
{
|
{
|
||||||
private OsuModDoubleTime mod;
|
private OsuModDoubleTime mod = null!;
|
||||||
|
|
||||||
[Params(1, 10, 100)]
|
[Params(1, 10, 100)]
|
||||||
public int Times { get; set; }
|
public int Times { get; set; }
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
@ -17,9 +15,9 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkRealmReads : BenchmarkTest
|
public class BenchmarkRealmReads : BenchmarkTest
|
||||||
{
|
{
|
||||||
private TemporaryNativeStorage storage;
|
private TemporaryNativeStorage storage = null!;
|
||||||
private RealmAccess realm;
|
private RealmAccess realm = null!;
|
||||||
private UpdateThread updateThread;
|
private UpdateThread updateThread = null!;
|
||||||
|
|
||||||
[Params(1, 100, 1000)]
|
[Params(1, 100, 1000)]
|
||||||
public int ReadsPerFetch { get; set; }
|
public int ReadsPerFetch { get; set; }
|
||||||
@ -135,9 +133,9 @@ namespace osu.Game.Benchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
realm?.Dispose();
|
realm.Dispose();
|
||||||
storage?.Dispose();
|
storage.Dispose();
|
||||||
updateThread?.Exit();
|
updateThread.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Engines;
|
using BenchmarkDotNet.Engines;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -13,9 +11,9 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkRuleset : BenchmarkTest
|
public class BenchmarkRuleset : BenchmarkTest
|
||||||
{
|
{
|
||||||
private OsuRuleset ruleset;
|
private OsuRuleset ruleset = null!;
|
||||||
private APIMod apiModDoubleTime;
|
private APIMod apiModDoubleTime = null!;
|
||||||
private APIMod apiModDifficultyAdjust;
|
private APIMod apiModDifficultyAdjust = null!;
|
||||||
|
|
||||||
public override void SetUp()
|
public override void SetUp()
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Configs;
|
using BenchmarkDotNet.Configs;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
|
@ -24,21 +24,24 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } },
|
new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } },
|
||||||
new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } },
|
new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } },
|
||||||
new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
|
new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
|
||||||
new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } },
|
|
||||||
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
||||||
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
|
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
|
||||||
new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } },
|
|
||||||
new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } },
|
|
||||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
|
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema, new[] { typeof(CatchModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
|
||||||
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
|
||||||
|
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||||
|
|
||||||
[TestCaseSource(nameof(catch_mod_mapping))]
|
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })]
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })]
|
||||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
|
||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
|
||||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
|
||||||
|
|
||||||
[TestCaseSource(nameof(catch_mod_mapping))]
|
|
||||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||||
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||||
AddStep("exit player", () => Player.Exit());
|
AddStep("exit player", () => Player.Exit());
|
||||||
CreateTest(null);
|
CreateTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAssert("legacy HUD combo counter hidden", () =>
|
AddAssert("legacy HUD combo counter hidden", () =>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
@ -31,9 +30,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values, onlineInfo);
|
||||||
|
|
||||||
StarRating = values[ATTRIB_ID_AIM];
|
StarRating = values[ATTRIB_ID_AIM];
|
||||||
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
|
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null)
|
public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame? lastFrame = null)
|
||||||
: base(time)
|
: base(time)
|
||||||
{
|
{
|
||||||
Position = position ?? -1;
|
Position = position ?? -1;
|
||||||
@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
Position = currentFrame.Position.X;
|
Position = currentFrame.Position.X;
|
||||||
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
|
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
|
||||||
|
@ -120,10 +120,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
lastHyperDashState = Catcher.HyperDashing;
|
lastHyperDashState = Catcher.HyperDashing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCatcherPosition(float X)
|
public void SetCatcherPosition(float x)
|
||||||
{
|
{
|
||||||
float lastPosition = Catcher.X;
|
float lastPosition = Catcher.X;
|
||||||
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
|
float newPosition = Math.Clamp(x, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
Catcher.X = newPosition;
|
Catcher.X = newPosition;
|
||||||
|
|
||||||
|
@ -23,10 +23,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } },
|
new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } },
|
||||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } },
|
new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } },
|
||||||
new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } },
|
new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } },
|
||||||
new object[] { LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) } },
|
|
||||||
new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } },
|
new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } },
|
||||||
new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } },
|
new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } },
|
||||||
new object[] { LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) } },
|
|
||||||
new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } },
|
new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } },
|
||||||
new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } },
|
new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } },
|
||||||
new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } },
|
new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } },
|
||||||
@ -34,7 +32,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } },
|
new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } },
|
||||||
new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } },
|
new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } },
|
||||||
new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } },
|
new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } },
|
||||||
new object[] { LegacyMods.Cinema, new[] { typeof(ManiaModCinema) } },
|
|
||||||
new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } },
|
new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } },
|
||||||
new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } },
|
new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } },
|
||||||
new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } },
|
new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } },
|
||||||
@ -45,12 +42,18 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema, new[] { typeof(ManiaModCinema) })]
|
||||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })]
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })]
|
||||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })]
|
||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
||||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||||
|
|
||||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
||||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
@ -20,12 +19,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
[JsonProperty("great_hit_window")]
|
[JsonProperty("great_hit_window")]
|
||||||
public double GreatHitWindow { get; set; }
|
public double GreatHitWindow { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The score multiplier applied via score-reducing mods.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("score_multiplier")]
|
|
||||||
public double ScoreMultiplier { get; set; }
|
|
||||||
|
|
||||||
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
||||||
{
|
{
|
||||||
foreach (var v in base.ToDatabaseAttributes())
|
foreach (var v in base.ToDatabaseAttributes())
|
||||||
@ -34,17 +27,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values, onlineInfo);
|
||||||
|
|
||||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||||
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
|
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
|
||||||
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
||||||
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
||||||
ScoreMultiplier = getScoreMultiplier(mods),
|
|
||||||
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
|
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -147,32 +146,5 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getScoreMultiplier(Mod[] mods)
|
|
||||||
{
|
|
||||||
double scoreMultiplier = 1;
|
|
||||||
|
|
||||||
foreach (var m in mods)
|
|
||||||
{
|
|
||||||
switch (m)
|
|
||||||
{
|
|
||||||
case ManiaModNoFail:
|
|
||||||
case ManiaModEasy:
|
|
||||||
case ManiaModHalfTime:
|
|
||||||
scoreMultiplier *= 0.5;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var maniaBeatmap = (ManiaBeatmap)Beatmap;
|
|
||||||
int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns;
|
|
||||||
|
|
||||||
if (diff > 0)
|
|
||||||
scoreMultiplier *= 0.9;
|
|
||||||
else if (diff < 0)
|
|
||||||
scoreMultiplier *= 0.9 + 0.04 * diff;
|
|
||||||
|
|
||||||
return scoreMultiplier;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
[JsonProperty("difficulty")]
|
[JsonProperty("difficulty")]
|
||||||
public double Difficulty { get; set; }
|
public double Difficulty { get; set; }
|
||||||
|
|
||||||
[JsonProperty("accuracy")]
|
|
||||||
public double Accuracy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("scaled_score")]
|
|
||||||
public double ScaledScore { get; set; }
|
|
||||||
|
|
||||||
public override IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
|
public override IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
|
||||||
{
|
{
|
||||||
foreach (var attribute in base.GetAttributesForDisplay())
|
foreach (var attribute in base.GetAttributesForDisplay())
|
||||||
yield return attribute;
|
yield return attribute;
|
||||||
|
|
||||||
yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty);
|
yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty);
|
||||||
yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
public class ManiaPerformanceCalculator : PerformanceCalculator
|
public class ManiaPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
||||||
// Score after being scaled by non-difficulty-increasing mods
|
|
||||||
private double scaledScore;
|
|
||||||
|
|
||||||
private int countPerfect;
|
private int countPerfect;
|
||||||
private int countGreat;
|
private int countGreat;
|
||||||
private int countGood;
|
private int countGood;
|
||||||
private int countOk;
|
private int countOk;
|
||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
private double scoreAccuracy;
|
||||||
|
|
||||||
public ManiaPerformanceCalculator()
|
public ManiaPerformanceCalculator()
|
||||||
: base(new ManiaRuleset())
|
: base(new ManiaRuleset())
|
||||||
@ -34,82 +32,47 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
var maniaAttributes = (ManiaDifficultyAttributes)attributes;
|
var maniaAttributes = (ManiaDifficultyAttributes)attributes;
|
||||||
|
|
||||||
scaledScore = score.TotalScore;
|
|
||||||
countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect);
|
countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect);
|
||||||
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||||
countGood = score.Statistics.GetValueOrDefault(HitResult.Good);
|
countGood = score.Statistics.GetValueOrDefault(HitResult.Good);
|
||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
|
scoreAccuracy = customAccuracy;
|
||||||
if (maniaAttributes.ScoreMultiplier > 0)
|
|
||||||
{
|
|
||||||
// Scale score up, so it's comparable to other keymods
|
|
||||||
scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
||||||
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
||||||
double multiplier = 0.8;
|
double multiplier = 8.0;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModNoFail))
|
if (score.Mods.Any(m => m is ModNoFail))
|
||||||
multiplier *= 0.9;
|
multiplier *= 0.75;
|
||||||
if (score.Mods.Any(m => m is ModEasy))
|
if (score.Mods.Any(m => m is ModEasy))
|
||||||
multiplier *= 0.5;
|
multiplier *= 0.5;
|
||||||
|
|
||||||
double difficultyValue = computeDifficultyValue(maniaAttributes);
|
double difficultyValue = computeDifficultyValue(maniaAttributes);
|
||||||
double accValue = computeAccuracyValue(difficultyValue, maniaAttributes);
|
double totalValue = difficultyValue * multiplier;
|
||||||
double totalValue =
|
|
||||||
Math.Pow(
|
|
||||||
Math.Pow(difficultyValue, 1.1) +
|
|
||||||
Math.Pow(accValue, 1.1), 1.0 / 1.1
|
|
||||||
) * multiplier;
|
|
||||||
|
|
||||||
return new ManiaPerformanceAttributes
|
return new ManiaPerformanceAttributes
|
||||||
{
|
{
|
||||||
Difficulty = difficultyValue,
|
Difficulty = difficultyValue,
|
||||||
Accuracy = accValue,
|
|
||||||
ScaledScore = scaledScore,
|
|
||||||
Total = totalValue
|
Total = totalValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
|
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
|
double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve
|
||||||
|
* Math.Max(0, 5 * scoreAccuracy - 4) // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
|
||||||
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
* (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus, capped at 1500 notes
|
||||||
|
|
||||||
if (scaledScore <= 500000)
|
|
||||||
difficultyValue = 0;
|
|
||||||
else if (scaledScore <= 600000)
|
|
||||||
difficultyValue *= (scaledScore - 500000) / 100000 * 0.3;
|
|
||||||
else if (scaledScore <= 700000)
|
|
||||||
difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
|
|
||||||
else if (scaledScore <= 800000)
|
|
||||||
difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
|
|
||||||
else if (scaledScore <= 900000)
|
|
||||||
difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
|
|
||||||
else
|
|
||||||
difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
|
|
||||||
|
|
||||||
return difficultyValue;
|
return difficultyValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes)
|
|
||||||
{
|
|
||||||
if (attributes.GreatHitWindow <= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Lots of arbitrary values from testing.
|
|
||||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
|
||||||
double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667)
|
|
||||||
* difficultyValue
|
|
||||||
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
|
|
||||||
|
|
||||||
return accuracyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
|
private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accuracy used to weight judgements independently from the score's actual accuracy.
|
||||||
|
/// </summary>
|
||||||
|
private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Filter;
|
using osu.Game.Rulesets.Filter;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateReleaseTime(HitObject currentObject, HitObject nextObject)
|
private double calculateReleaseTime(HitObject currentObject, HitObject? nextObject)
|
||||||
{
|
{
|
||||||
double endTime = currentObject.GetEndTime();
|
double endTime = currentObject.GetEndTime();
|
||||||
|
|
||||||
@ -96,10 +95,10 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
bool canDelayKeyUpFully = nextObject == null ||
|
bool canDelayKeyUpFully = nextObject == null ||
|
||||||
nextObject.StartTime > endTime + RELEASE_DELAY;
|
nextObject.StartTime > endTime + RELEASE_DELAY;
|
||||||
|
|
||||||
return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9);
|
return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.AsNonNull().StartTime - endTime) * 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject GetNextObject(int currentIndex)
|
protected override HitObject? GetNextObject(int currentIndex)
|
||||||
{
|
{
|
||||||
int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
|
int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
Actions.AddRange(actions);
|
Actions.AddRange(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@ -41,10 +40,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
private bool editorComponentsReady => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
|
||||||
&& editor.ChildrenOfType<TimelineArea>().FirstOrDefault()?.IsLoaded == true
|
|
||||||
&& editor?.ChildrenOfType<Playfield>().FirstOrDefault()?.IsLoaded == true;
|
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
|
public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
|
||||||
@ -52,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
double? velocity = null;
|
double? velocity = null;
|
||||||
|
|
||||||
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
|
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
AddUntilStep("wait for editor load", () => editorComponentsReady);
|
AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
|
||||||
|
|
||||||
AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
|
AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||||
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
|
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
|
||||||
@ -91,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddStep("exit", () => InputManager.Key(Key.Escape));
|
AddStep("exit", () => InputManager.Key(Key.Escape));
|
||||||
|
|
||||||
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
|
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
AddUntilStep("wait for editor load", () => editorComponentsReady);
|
AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
|
||||||
|
|
||||||
AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
|
AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
|
||||||
AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
|
AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
|
||||||
|
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs
Normal file
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModRepel : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(0.1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1)]
|
||||||
|
public void TestRepel(float strength)
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRepel
|
||||||
|
{
|
||||||
|
RepulsionStrength = { Value = strength },
|
||||||
|
},
|
||||||
|
PassCondition = () => true,
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModSingleTap : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1500,
|
||||||
|
Position = new Vector2(300, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2000,
|
||||||
|
Position = new Vector2(400, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||||
|
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||||
|
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures singletapping is reset before the first hitobject after intro.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
// first press during intro.
|
||||||
|
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(200)),
|
||||||
|
// press different key at hitobject and ensure it has been hit.
|
||||||
|
new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures singletapping is reset before the first hitobject after a break.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
Breaks = new List<BreakPeriod>
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 2000),
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2500,
|
||||||
|
Position = new Vector2(500, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(500, 100),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
// first press to start singletap lock.
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
// press different key after break but before hit object.
|
||||||
|
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
||||||
|
// press same key at second hitobject and ensure it has been hit.
|
||||||
|
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2501, new Vector2(500, 100)),
|
||||||
|
// press different key at third hitobject and ensure it has been missed.
|
||||||
|
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(3001, new Vector2(500, 100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -25,24 +25,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } },
|
new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } },
|
||||||
new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } },
|
new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } },
|
||||||
new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } },
|
new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } },
|
||||||
new object[] { LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) } },
|
|
||||||
new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } },
|
new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } },
|
||||||
new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } },
|
new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } },
|
||||||
new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } },
|
new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } },
|
||||||
new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } },
|
new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } },
|
||||||
new object[] { LegacyMods.Perfect, new[] { typeof(OsuModPerfect) } },
|
|
||||||
new object[] { LegacyMods.Cinema, new[] { typeof(OsuModCinema) } },
|
|
||||||
new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } },
|
new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } },
|
||||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }
|
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(osu_mod_mapping))]
|
[TestCaseSource(nameof(osu_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema, new[] { typeof(OsuModCinema) })]
|
||||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })]
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })]
|
||||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })]
|
||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
|
||||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||||
|
|
||||||
[TestCaseSource(nameof(osu_mod_mapping))]
|
[TestCaseSource(nameof(osu_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
|
||||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||||
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
||||||
AddAssert("ball is white", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.White);
|
AddAssert("ball is white", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.White);
|
||||||
|
|
||||||
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
||||||
AddAssert("ball is red", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.Red);
|
AddAssert("ball is red", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Slider prepareObject(Slider slider)
|
private Slider prepareObject(Slider slider)
|
||||||
|
120
osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
Normal file
120
osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// 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.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneSliderFollowCircleInput : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
private List<JudgementResult>? judgementResults;
|
||||||
|
private ScoreAccessibleReplayPlayer? currentPlayer;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaximumDistanceTrackingWithoutMovement(
|
||||||
|
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||||
|
float circleSize,
|
||||||
|
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||||
|
double velocity)
|
||||||
|
{
|
||||||
|
const double time_slider_start = 1000;
|
||||||
|
|
||||||
|
float circleRadius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (circleSize - 5) / 5) / 2;
|
||||||
|
float followCircleRadius = circleRadius * 1.2f;
|
||||||
|
|
||||||
|
performTest(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = time_slider_start,
|
||||||
|
Position = new Vector2(0, 0),
|
||||||
|
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity },
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(followCircleRadius, 0),
|
||||||
|
}, followCircleRadius),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = circleSize,
|
||||||
|
SliderTickRate = 1
|
||||||
|
},
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
}, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Position = new Vector2(-circleRadius + 1, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Tracking kept", assertMaxJudge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool assertMaxJudge() => judgementResults?.Any() == true && judgementResults.All(t => t.Type == t.Judgement.MaxResult);
|
||||||
|
|
||||||
|
private void performTest(Beatmap<OsuHitObject> beatmap, List<ReplayFrame> frames)
|
||||||
|
{
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
p.OnLoadComplete += _ =>
|
||||||
|
{
|
||||||
|
p.ScoreProcessor.NewJudgement += result =>
|
||||||
|
{
|
||||||
|
if (currentPlayer == p) judgementResults?.Add(result);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LoadScreen(currentPlayer = p);
|
||||||
|
judgementResults = new List<JudgementResult>();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
AddUntilStep("Wait for completion", () => currentPlayer?.ScoreProcessor.HasCompleted.Value == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -66,18 +67,25 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
drawableSlider = null;
|
drawableSlider = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
[SetUpSteps]
|
protected override bool HasCustomSteps => true;
|
||||||
public override void SetUpSteps()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(0)]
|
[TestCase(0)]
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
|
[FlakyTest]
|
||||||
|
/*
|
||||||
|
* Fail rate around 0.15%
|
||||||
|
*
|
||||||
|
* TearDown : System.TimeoutException : "wait for seek to finish" timed out
|
||||||
|
* --TearDown
|
||||||
|
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
|
||||||
|
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
|
||||||
|
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
|
||||||
|
*/
|
||||||
public void TestSnakingEnabled(int sliderIndex)
|
public void TestSnakingEnabled(int sliderIndex)
|
||||||
{
|
{
|
||||||
AddStep("enable autoplay", () => autoplay = true);
|
AddStep("enable autoplay", () => autoplay = true);
|
||||||
base.SetUpSteps();
|
CreateTest();
|
||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
retrieveSlider(sliderIndex);
|
retrieveSlider(sliderIndex);
|
||||||
@ -98,10 +106,20 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestCase(0)]
|
[TestCase(0)]
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
|
[FlakyTest]
|
||||||
|
/*
|
||||||
|
* Fail rate around 0.15%
|
||||||
|
*
|
||||||
|
* TearDown : System.TimeoutException : "wait for seek to finish" timed out
|
||||||
|
* --TearDown
|
||||||
|
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
|
||||||
|
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
|
||||||
|
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
|
||||||
|
*/
|
||||||
public void TestSnakingDisabled(int sliderIndex)
|
public void TestSnakingDisabled(int sliderIndex)
|
||||||
{
|
{
|
||||||
AddStep("have autoplay", () => autoplay = true);
|
AddStep("have autoplay", () => autoplay = true);
|
||||||
base.SetUpSteps();
|
CreateTest();
|
||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
retrieveSlider(sliderIndex);
|
retrieveSlider(sliderIndex);
|
||||||
@ -121,8 +139,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
AddStep("enable autoplay", () => autoplay = true);
|
AddStep("enable autoplay", () => autoplay = true);
|
||||||
setSnaking(true);
|
setSnaking(true);
|
||||||
base.SetUpSteps();
|
CreateTest();
|
||||||
|
|
||||||
// repeat might have a chance to update its position depending on where in the frame its hit,
|
// repeat might have a chance to update its position depending on where in the frame its hit,
|
||||||
// so some leniency is allowed here instead of checking strict equality
|
// so some leniency is allowed here instead of checking strict equality
|
||||||
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame);
|
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame);
|
||||||
@ -133,15 +150,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
AddStep("disable autoplay", () => autoplay = false);
|
AddStep("disable autoplay", () => autoplay = false);
|
||||||
setSnaking(true);
|
setSnaking(true);
|
||||||
base.SetUpSteps();
|
CreateTest();
|
||||||
|
|
||||||
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased);
|
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveSlider(int index)
|
private void retrieveSlider(int index)
|
||||||
{
|
{
|
||||||
AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]);
|
AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]);
|
||||||
addSeekStep(() => slider);
|
addSeekStep(() => slider.StartTime);
|
||||||
AddUntilStep("retrieve drawable slider", () =>
|
AddUntilStep("retrieve drawable slider", () =>
|
||||||
(drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
|
(drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
|
||||||
}
|
}
|
||||||
@ -205,16 +221,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSeekStep(Func<Slider> slider)
|
private void addSeekStep(Func<double> getTime)
|
||||||
{
|
{
|
||||||
AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime));
|
AddStep("seek to time", () => Player.GameplayClockContainer.Seek(getTime()));
|
||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(getTime(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
}
|
|
||||||
|
|
||||||
private void addSeekStep(Func<double> time)
|
|
||||||
{
|
|
||||||
AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time()));
|
|
||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() };
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() };
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.17.2" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -26,6 +25,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
[JsonProperty("speed_difficulty")]
|
[JsonProperty("speed_difficulty")]
|
||||||
public double SpeedDifficulty { get; set; }
|
public double SpeedDifficulty { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of clickable objects weighted by difficulty.
|
||||||
|
/// Related to <see cref="SpeedDifficulty"/>
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("speed_note_count")]
|
||||||
|
public double SpeedNoteCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The difficulty corresponding to the flashlight skill.
|
/// The difficulty corresponding to the flashlight skill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -94,11 +100,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
|
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
|
||||||
|
|
||||||
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
|
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
|
||||||
|
yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values, onlineInfo);
|
||||||
|
|
||||||
AimDifficulty = values[ATTRIB_ID_AIM];
|
AimDifficulty = values[ATTRIB_ID_AIM];
|
||||||
SpeedDifficulty = values[ATTRIB_ID_SPEED];
|
SpeedDifficulty = values[ATTRIB_ID_SPEED];
|
||||||
@ -108,6 +115,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
|
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
|
||||||
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
|
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
|
||||||
|
SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT];
|
||||||
|
|
||||||
|
DrainRate = onlineInfo.DrainRate;
|
||||||
|
HitCircleCount = onlineInfo.CircleCount;
|
||||||
|
SliderCount = onlineInfo.SliderCount;
|
||||||
|
SpinnerCount = onlineInfo.SpinnerCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Newtonsoft.Json implicit ShouldSerialize() methods
|
#region Newtonsoft.Json implicit ShouldSerialize() methods
|
||||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||||
double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||||
double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
|
double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
|
||||||
|
double speedNotes = ((Speed)skills[2]).RelevantNoteCount();
|
||||||
double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
|
double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
|
||||||
|
|
||||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||||
@ -75,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
Mods = mods,
|
Mods = mods,
|
||||||
AimDifficulty = aimRating,
|
AimDifficulty = aimRating,
|
||||||
SpeedDifficulty = speedRating,
|
SpeedDifficulty = speedRating,
|
||||||
|
SpeedNoteCount = speedNotes,
|
||||||
FlashlightDifficulty = flashlightRating,
|
FlashlightDifficulty = flashlightRating,
|
||||||
SliderFactor = sliderFactor,
|
SliderFactor = sliderFactor,
|
||||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||||
|
@ -163,8 +163,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate accuracy assuming the worst case scenario
|
||||||
|
double relevantTotalDiff = totalHits - attributes.SpeedNoteCount;
|
||||||
|
double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff);
|
||||||
|
double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat));
|
||||||
|
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
|
||||||
|
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0);
|
||||||
|
|
||||||
// Scale the speed value with accuracy and OD.
|
// 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);
|
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||||
|
|
||||||
// Scale the speed value with # of 50s to punish doubletapping.
|
// Scale the speed value with # of 50s to punish doubletapping.
|
||||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled.
|
/// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Flashlight : OsuStrainSkill
|
public class Flashlight : StrainSkill
|
||||||
{
|
{
|
||||||
private readonly bool hasHiddenMod;
|
private readonly bool hasHiddenMod;
|
||||||
|
|
||||||
@ -27,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double skillMultiplier => 0.05;
|
private double skillMultiplier => 0.05;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
protected override double DecayWeight => 1.0;
|
|
||||||
|
|
||||||
private double currentStrain;
|
private double currentStrain;
|
||||||
|
|
||||||
@ -42,5 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
return currentStrain;
|
return currentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
public abstract class OsuStrainSkill : StrainSkill
|
public abstract class OsuStrainSkill : StrainSkill
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default multiplier applied by <see cref="OsuStrainSkill"/> to the final difficulty value after all other calculations.
|
||||||
|
/// May be overridden via <see cref="DifficultyMultiplier"/>.
|
||||||
|
/// </summary>
|
||||||
|
public const double DEFAULT_DIFFICULTY_MULTIPLIER = 1.06;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||||
/// This is done in order to decrease their impact on the overall difficulty of the map for this skill.
|
/// This is done in order to decrease their impact on the overall difficulty of the map for this skill.
|
||||||
@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The final multiplier to be applied to <see cref="DifficultyValue"/> after all other calculations.
|
/// The final multiplier to be applied to <see cref="DifficultyValue"/> after all other calculations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual double DifficultyMultiplier => 1.06;
|
protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
protected OsuStrainSkill(Mod[] mods)
|
protected OsuStrainSkill(Mod[] mods)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
|
@ -8,6 +8,8 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
protected override double DifficultyMultiplier => 1.04;
|
protected override double DifficultyMultiplier => 1.04;
|
||||||
private readonly double greatWindow;
|
private readonly double greatWindow;
|
||||||
|
|
||||||
|
private readonly List<double> objectStrains = new List<double>();
|
||||||
|
|
||||||
public Speed(Mod[] mods, double hitWindowGreat)
|
public Speed(Mod[] mods, double hitWindowGreat)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
@ -43,7 +47,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow);
|
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow);
|
||||||
|
|
||||||
return currentStrain * currentRhythm;
|
double totalStrain = currentStrain * currentRhythm;
|
||||||
|
|
||||||
|
objectStrains.Add(totalStrain);
|
||||||
|
|
||||||
|
return totalStrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double RelevantNoteCount()
|
||||||
|
{
|
||||||
|
if (objectStrains.Count == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double maxStrain = objectStrains.Max();
|
||||||
|
|
||||||
|
if (maxStrain == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// 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 osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
private const double flash_duration = 1000;
|
||||||
|
|
||||||
|
private DrawableRuleset<OsuHitObject> ruleset = null!;
|
||||||
|
|
||||||
|
protected OsuAction? LastAcceptedAction { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||||
|
/// </remarks>
|
||||||
|
private PeriodTracker nonGameplayPeriods = null!;
|
||||||
|
|
||||||
|
private IFrameStableClock gameplayClock = null!;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ruleset = drawableRuleset;
|
||||||
|
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||||
|
|
||||||
|
var periods = new List<Period>();
|
||||||
|
|
||||||
|
if (drawableRuleset.Objects.Any())
|
||||||
|
{
|
||||||
|
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||||
|
|
||||||
|
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||||
|
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||||
|
|
||||||
|
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
nonGameplayPeriods = new PeriodTracker(periods);
|
||||||
|
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||||
|
|
||||||
|
private bool checkCorrectAction(OsuAction action)
|
||||||
|
{
|
||||||
|
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
|
{
|
||||||
|
LastAcceptedAction = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case OsuAction.LeftButton:
|
||||||
|
case OsuAction.RightButton:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Any action which is not left or right button should be ignored.
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckValidNewAction(action))
|
||||||
|
{
|
||||||
|
LastAcceptedAction = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private readonly InputBlockingMod mod;
|
||||||
|
|
||||||
|
public InputInterceptor(InputBlockingMod mod)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||||
|
=> !mod.checkCorrectAction(e.Action);
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,119 +1,20 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Beatmaps.Timing;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Utils;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
public class OsuModAlternate : InputBlockingMod
|
||||||
{
|
{
|
||||||
public override string Name => @"Alternate";
|
public override string Name => @"Alternate";
|
||||||
public override string Acronym => @"AL";
|
public override string Acronym => @"AL";
|
||||||
public override string Description => @"Don't use the same key twice in a row!";
|
public override string Description => @"Don't use the same key twice in a row!";
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
|
||||||
public override ModType Type => ModType.Conversion;
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
private const double flash_duration = 1000;
|
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
|
||||||
/// </remarks>
|
|
||||||
private PeriodTracker nonGameplayPeriods;
|
|
||||||
|
|
||||||
private OsuAction? lastActionPressed;
|
|
||||||
private DrawableRuleset<OsuHitObject> ruleset;
|
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock;
|
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
|
||||||
{
|
|
||||||
ruleset = drawableRuleset;
|
|
||||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
|
||||||
|
|
||||||
var periods = new List<Period>();
|
|
||||||
|
|
||||||
if (drawableRuleset.Objects.Any())
|
|
||||||
{
|
|
||||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
|
||||||
|
|
||||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
|
||||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
|
||||||
|
|
||||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
|
||||||
}
|
|
||||||
|
|
||||||
nonGameplayPeriods = new PeriodTracker(periods);
|
|
||||||
|
|
||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool checkCorrectAction(OsuAction action)
|
|
||||||
{
|
|
||||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
|
||||||
{
|
|
||||||
lastActionPressed = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case OsuAction.LeftButton:
|
|
||||||
case OsuAction.RightButton:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Any action which is not left or right button should be ignored.
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastActionPressed != action)
|
|
||||||
{
|
|
||||||
// User alternated correctly.
|
|
||||||
lastActionPressed = action;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
|
||||||
{
|
|
||||||
private readonly OsuModAlternate mod;
|
|
||||||
|
|
||||||
public InputInterceptor(OsuModAlternate mod)
|
|
||||||
{
|
|
||||||
this.mod = mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
|
||||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
|
||||||
=> !mod.checkCorrectAction(e.Action);
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
public bool PerformFail() => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModAutoplay : ModAutoplay
|
public class OsuModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
|
@ -30,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
[SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")]
|
[SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")]
|
||||||
public Bindable<bool> ClassicNoteLock { get; } = new BindableBool(true);
|
public Bindable<bool> ClassicNoteLock { get; } = new BindableBool(true);
|
||||||
|
|
||||||
[SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")]
|
|
||||||
public Bindable<bool> FixedFollowCircleHitArea { get; } = new BindableBool(true);
|
|
||||||
|
|
||||||
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
|
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
|
||||||
public Bindable<bool> AlwaysPlayTailSample { get; } = new BindableBool(true);
|
public Bindable<bool> AlwaysPlayTailSample { get; } = new BindableBool(true);
|
||||||
|
|
||||||
@ -62,10 +59,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case DrawableSlider slider:
|
|
||||||
slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DrawableSliderHead head:
|
case DrawableSliderHead head:
|
||||||
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
||||||
break;
|
break;
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock;
|
private IFrameStableClock gameplayClock;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How early before a hitobject's start time to trigger a hit.
|
/// How early before a hitobject's start time to trigger a hit.
|
||||||
|
98
osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
Normal file
98
osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
internal class OsuModRepel : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "Repel";
|
||||||
|
public override string Acronym => "RP";
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override string Description => "Hit objects run away!";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
||||||
|
|
||||||
|
private IFrameStableClock? gameplayClock;
|
||||||
|
|
||||||
|
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||||
|
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||||
|
{
|
||||||
|
Precision = 0.05f,
|
||||||
|
MinValue = 0.05f,
|
||||||
|
MaxValue = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
|
||||||
|
// Hide judgment displays and follow points as they won't make any sense.
|
||||||
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||||
|
|
||||||
|
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||||
|
{
|
||||||
|
var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
|
if (drawable.HitObject is Slider thisSlider)
|
||||||
|
{
|
||||||
|
var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider);
|
||||||
|
|
||||||
|
destination = Vector2.Clamp(
|
||||||
|
destination,
|
||||||
|
new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top),
|
||||||
|
new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
easeTo(circle, destination, cursorPos);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSlider slider:
|
||||||
|
|
||||||
|
if (!slider.HeadCircle.Result.HasResult)
|
||||||
|
easeTo(slider, destination, cursorPos);
|
||||||
|
else
|
||||||
|
easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
Debug.Assert(gameplayClock != null);
|
||||||
|
|
||||||
|
double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04);
|
||||||
|
|
||||||
|
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
|
||||||
|
hitObject.Position = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModSingleTap : InputBlockingMod
|
||||||
|
{
|
||||||
|
public override string Name => @"Single Tap";
|
||||||
|
public override string Acronym => @"ST";
|
||||||
|
public override string Description => @"You must only use one key!";
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||||
|
|
||||||
|
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Everything rotates. EVERYTHING.";
|
public override string Description => "Everything rotates. EVERYTHING.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private float theta;
|
private float theta;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "They just won't stay still...";
|
public override string Description => "They just won't stay still...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
||||||
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
||||||
|
@ -29,7 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public DrawableSliderHead HeadCircle => headContainer.Child;
|
public DrawableSliderHead HeadCircle => headContainer.Child;
|
||||||
public DrawableSliderTail TailCircle => tailContainer.Child;
|
public DrawableSliderTail TailCircle => tailContainer.Child;
|
||||||
|
|
||||||
public SliderBall Ball { get; private set; }
|
[Cached]
|
||||||
|
public DrawableSliderBall Ball { get; private set; }
|
||||||
|
|
||||||
public SkinnableDrawable Body { get; private set; }
|
public SkinnableDrawable Body { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -60,6 +62,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public DrawableSlider([CanBeNull] Slider s = null)
|
public DrawableSlider([CanBeNull] Slider s = null)
|
||||||
: base(s)
|
: base(s)
|
||||||
{
|
{
|
||||||
|
Ball = new DrawableSliderBall
|
||||||
|
{
|
||||||
|
GetInitialHitAction = () => HeadCircle.HitAction,
|
||||||
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -73,13 +82,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
||||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||||
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
||||||
Ball = new SliderBall(this)
|
Ball,
|
||||||
{
|
|
||||||
GetInitialHitAction = () => HeadCircle.HitAction,
|
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0
|
|
||||||
},
|
|
||||||
slidingSample = new PausableSkinnableSound { Looping = true }
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -316,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
const float fade_out_time = 450;
|
const float fade_out_time = 450;
|
||||||
|
|
||||||
// intentionally pile on an extra FadeOut to make it happen much faster.
|
|
||||||
Ball.FadeOut(fade_out_time / 4, Easing.Out);
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
|
||||||
if (SliderBody?.SnakingOut.Value == true)
|
if (SliderBody?.SnakingOut.Value == true)
|
||||||
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
||||||
break;
|
break;
|
||||||
|
@ -7,25 +7,24 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
||||||
{
|
{
|
||||||
|
public const float FOLLOW_AREA = 2.4f;
|
||||||
|
|
||||||
public Func<OsuAction?> GetInitialHitAction;
|
public Func<OsuAction?> GetInitialHitAction;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -34,19 +33,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
set => ball.Colour = value;
|
set => ball.Colour = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private Drawable followCircleReceptor;
|
||||||
/// Whether to track accurately to the visual size of this <see cref="SliderBall"/>.
|
private DrawableSlider drawableSlider;
|
||||||
/// If <c>false</c>, tracking will be performed at the final scale at all times.
|
private Drawable ball;
|
||||||
/// </summary>
|
|
||||||
public bool InputTracksVisualSize = true;
|
|
||||||
|
|
||||||
private readonly Drawable followCircle;
|
[BackgroundDependencyLoader]
|
||||||
private readonly DrawableSlider drawableSlider;
|
private void load(DrawableHitObject drawableSlider)
|
||||||
private readonly Drawable ball;
|
|
||||||
|
|
||||||
public SliderBall(DrawableSlider drawableSlider)
|
|
||||||
{
|
{
|
||||||
this.drawableSlider = drawableSlider;
|
this.drawableSlider = (DrawableSlider)drawableSlider;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
@ -54,13 +48,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
followCircle = new FollowCircleContainer
|
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle())
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0,
|
},
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
|
followCircleReceptor = new CircularContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true
|
||||||
},
|
},
|
||||||
ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall())
|
ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall())
|
||||||
{
|
{
|
||||||
@ -104,15 +103,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
tracking = value;
|
tracking = value;
|
||||||
|
|
||||||
if (InputTracksVisualSize)
|
followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f);
|
||||||
followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration.
|
|
||||||
followCircle.ScaleTo(tracking ? 2.4f : 1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
// in valid time range
|
// in valid time range
|
||||||
Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime &&
|
Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime &&
|
||||||
// in valid position range
|
// in valid position range
|
||||||
lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
|
lastScreenSpaceMousePosition.HasValue && followCircleReceptor.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
|
||||||
// valid action
|
// valid action
|
||||||
(actions?.Any(isValidTrackingAction) ?? false);
|
(actions?.Any(isValidTrackingAction) ?? false);
|
||||||
|
|
||||||
@ -208,74 +199,5 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
||||||
lastPosition = Position;
|
lastPosition = Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FollowCircleContainer : CircularContainer
|
|
||||||
{
|
|
||||||
public override bool HandlePositionalInput => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DefaultFollowCircle : CompositeDrawable
|
|
||||||
{
|
|
||||||
public DefaultFollowCircle()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
InternalChild = new CircularContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
BorderThickness = 5,
|
|
||||||
BorderColour = Color4.Orange,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Child = new Box
|
|
||||||
{
|
|
||||||
Colour = Color4.Orange,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0.2f,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DefaultSliderBall : CompositeDrawable
|
|
||||||
{
|
|
||||||
private Box box;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
|
||||||
{
|
|
||||||
var slider = (DrawableSlider)drawableObject;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
|
||||||
|
|
||||||
InternalChild = new CircularContainer
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
BorderThickness = 10,
|
|
||||||
BorderColour = Color4.White,
|
|
||||||
Alpha = 1,
|
|
||||||
Child = box = new Box
|
|
||||||
{
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.White,
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
slider.Tracking.BindValueChanged(trackingChanged, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
|
||||||
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModClassic(),
|
new OsuModClassic(),
|
||||||
new OsuModRandom(),
|
new OsuModRandom(),
|
||||||
new OsuModMirror(),
|
new OsuModMirror(),
|
||||||
new OsuModAlternate(),
|
new MultiMod(new OsuModAlternate(), new OsuModSingleTap())
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
new OsuModMagnetised(),
|
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||||
new ModAdaptiveSpeed()
|
new ModAdaptiveSpeed()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -95,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
{
|
{
|
||||||
double endTime = prev.GetEndTime();
|
double endTime = prev.GetEndTime();
|
||||||
|
|
||||||
HitWindows hitWindows = null;
|
HitWindows? hitWindows = null;
|
||||||
|
|
||||||
switch (h)
|
switch (h)
|
||||||
{
|
{
|
||||||
@ -245,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
}
|
}
|
||||||
|
|
||||||
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
||||||
OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
|
OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
|
||||||
|
|
||||||
if (timeDifference > 0)
|
if (timeDifference > 0)
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
Actions.AddRange(actions);
|
Actions.AddRange(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
Position = currentFrame.Position;
|
Position = currentFrame.Position;
|
||||||
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultFollowCircle : FollowCircle
|
||||||
|
{
|
||||||
|
public DefaultFollowCircle()
|
||||||
|
{
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 5,
|
||||||
|
BorderColour = Color4.Orange,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Orange,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||||
|
{
|
||||||
|
const float scale_duration = 300f;
|
||||||
|
const float fade_duration = 300f;
|
||||||
|
|
||||||
|
this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint)
|
||||||
|
.FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
const float fade_duration = 450f;
|
||||||
|
|
||||||
|
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||||
|
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs
Normal file
111
osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultSliderBall : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Box box = null!;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
BorderThickness = 10,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
Alpha = 1,
|
||||||
|
Child = box = new Box
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
var slider = (DrawableSlider)parentObject;
|
||||||
|
slider.Tracking.BindValueChanged(trackingChanged, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
||||||
|
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float fade_duration = 450f;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeIn()
|
||||||
|
.ScaleTo(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||||
|
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
this.ScaleTo(1.4f, fade_duration, Easing.Out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
complete.BindValueChanged(complete => updateDiscColour(complete.NewValue, 200));
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
@ -137,6 +137,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
this.ScaleTo(initial_scale);
|
this.ScaleTo(initial_scale);
|
||||||
this.RotateTo(0);
|
this.RotateTo(0);
|
||||||
|
|
||||||
|
updateDiscColour(false);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
{
|
{
|
||||||
// constant ambient rotation to give the spinner "spinning" character.
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
@ -177,12 +179,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
if (drawableSpinner.Result?.TimeCompleted is double completionTime)
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
{
|
||||||
updateComplete(state == ArmedState.Hit, 0);
|
using (BeginAbsoluteSequence(completionTime))
|
||||||
|
updateDiscColour(true, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateComplete(bool complete, double duration)
|
private void updateDiscColour(bool complete, double duration = 0)
|
||||||
{
|
{
|
||||||
var colour = complete ? completeColour : normalColour;
|
var colour = complete ? completeColour : normalColour;
|
||||||
|
|
||||||
|
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
|
{
|
||||||
|
public abstract class FollowCircle : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
protected DrawableHitObject? ParentObject { get; private set; }
|
||||||
|
|
||||||
|
protected FollowCircle()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (ParentObject != null)
|
||||||
|
{
|
||||||
|
ParentObject.HitObjectApplied += onHitObjectApplied;
|
||||||
|
onHitObjectApplied(ParentObject);
|
||||||
|
|
||||||
|
ParentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(ParentObject, ParentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHitObjectApplied(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
this.ScaleTo(1f)
|
||||||
|
.FadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
OnSliderEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (ParentObject != null)
|
||||||
|
{
|
||||||
|
ParentObject.HitObjectApplied -= onHitObjectApplied;
|
||||||
|
ParentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void OnTrackingChanged(ValueChangedEvent<bool> tracking);
|
||||||
|
|
||||||
|
protected abstract void OnSliderEnd();
|
||||||
|
}
|
||||||
|
}
|
55
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs
Normal file
55
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyFollowCircle : FollowCircle
|
||||||
|
{
|
||||||
|
public LegacyFollowCircle(Drawable animationContent)
|
||||||
|
{
|
||||||
|
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||||
|
animationContent.Scale *= 0.5f;
|
||||||
|
animationContent.Anchor = Anchor.Centre;
|
||||||
|
animationContent.Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
InternalChild = animationContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||||
|
{
|
||||||
|
Debug.Assert(ParentObject != null);
|
||||||
|
|
||||||
|
if (ParentObject.Judged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current);
|
||||||
|
|
||||||
|
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||||
|
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||||
|
if (tracking.NewValue)
|
||||||
|
{
|
||||||
|
// TODO: Follow circle should bounce on each slider tick.
|
||||||
|
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||||
|
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Should animate only at the next slider tick if we want to match stable perfectly.
|
||||||
|
this.ScaleTo(4f, 100)
|
||||||
|
.FadeTo(0f, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
this.ScaleTo(1.6f, 200, Easing.Out)
|
||||||
|
.FadeOut(200, Easing.In);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -18,8 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
private readonly ISkin skin;
|
private readonly ISkin skin;
|
||||||
|
|
||||||
private Sprite layerNd;
|
[Resolved(canBeNull: true)]
|
||||||
private Sprite layerSpec;
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
|
private Sprite layerNd = null!;
|
||||||
|
private Sprite layerSpec = null!;
|
||||||
|
|
||||||
public LegacySliderBall(Drawable animationContent, ISkin skin)
|
public LegacySliderBall(Drawable animationContent, ISkin skin)
|
||||||
{
|
{
|
||||||
@ -58,6 +61,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
@ -68,5 +82,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
layerNd.Rotation = -appliedRotation;
|
layerNd.Rotation = -appliedRotation;
|
||||||
layerSpec.Rotation = -appliedRotation;
|
layerSpec.Rotation = -appliedRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which in this case have no visual impact (due to
|
||||||
|
// instant fade) but may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||||
|
this.FadeIn();
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
this.FadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
return this.GetAnimation(component.LookupName, false, false);
|
return this.GetAnimation(component.LookupName, false, false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||||
if (followCircle != null)
|
if (followCircleContent != null)
|
||||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
return new LegacyFollowCircle(followCircleContent);
|
||||||
followCircle.Scale *= 0.5f;
|
|
||||||
return followCircle;
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.SliderBall:
|
case OsuSkinComponents.SliderBall:
|
||||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||||
|
@ -194,7 +194,28 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
private static Vector2 clampSliderToPlayfield(WorkingObject workingObject)
|
private static Vector2 clampSliderToPlayfield(WorkingObject workingObject)
|
||||||
{
|
{
|
||||||
var slider = (Slider)workingObject.HitObject;
|
var slider = (Slider)workingObject.HitObject;
|
||||||
var possibleMovementBounds = calculatePossibleMovementBounds(slider);
|
var possibleMovementBounds = CalculatePossibleMovementBounds(slider);
|
||||||
|
|
||||||
|
// The slider rotation applied in computeModifiedPosition might make it impossible to fit the slider into the playfield
|
||||||
|
// For example, a long horizontal slider will be off-screen when rotated by 90 degrees
|
||||||
|
// In this case, limit the rotation to either 0 or 180 degrees
|
||||||
|
if (possibleMovementBounds.Width < 0 || possibleMovementBounds.Height < 0)
|
||||||
|
{
|
||||||
|
float currentRotation = getSliderRotation(slider);
|
||||||
|
float diff1 = getAngleDifference(workingObject.RotationOriginal, currentRotation);
|
||||||
|
float diff2 = getAngleDifference(workingObject.RotationOriginal + MathF.PI, currentRotation);
|
||||||
|
|
||||||
|
if (diff1 < diff2)
|
||||||
|
{
|
||||||
|
RotateSlider(slider, workingObject.RotationOriginal - getSliderRotation(slider));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider));
|
||||||
|
}
|
||||||
|
|
||||||
|
possibleMovementBounds = CalculatePossibleMovementBounds(slider);
|
||||||
|
}
|
||||||
|
|
||||||
var previousPosition = workingObject.PositionModified;
|
var previousPosition = workingObject.PositionModified;
|
||||||
|
|
||||||
@ -239,10 +260,12 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
||||||
/// such that the entire slider is inside the playfield.
|
/// such that the entire slider is inside the playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="slider">The <see cref="Slider"/> for which to calculate a movement bounding box.</param>
|
||||||
|
/// <returns>A <see cref="RectangleF"/> which contains all of the possible movements of the slider such that the entire slider is inside the playfield.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static RectangleF calculatePossibleMovementBounds(Slider slider)
|
public static RectangleF CalculatePossibleMovementBounds(Slider slider)
|
||||||
{
|
{
|
||||||
var pathPositions = new List<Vector2>();
|
var pathPositions = new List<Vector2>();
|
||||||
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
||||||
@ -353,6 +376,18 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
|
return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the absolute difference between 2 angles measured in Radians.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="angle1">The first angle</param>
|
||||||
|
/// <param name="angle2">The second angle</param>
|
||||||
|
/// <returns>The absolute difference with interval <c>[0, MathF.PI)</c></returns>
|
||||||
|
private static float getAngleDifference(float angle1, float angle2)
|
||||||
|
{
|
||||||
|
float diff = MathF.Abs(angle1 - angle2) % (MathF.PI * 2);
|
||||||
|
return MathF.Min(diff, MathF.PI * 2 - diff);
|
||||||
|
}
|
||||||
|
|
||||||
public class ObjectPositionInfo
|
public class ObjectPositionInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -395,6 +430,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
|
|
||||||
private class WorkingObject
|
private class WorkingObject
|
||||||
{
|
{
|
||||||
|
public float RotationOriginal { get; }
|
||||||
public Vector2 PositionOriginal { get; }
|
public Vector2 PositionOriginal { get; }
|
||||||
public Vector2 PositionModified { get; set; }
|
public Vector2 PositionModified { get; set; }
|
||||||
public Vector2 EndPositionModified { get; set; }
|
public Vector2 EndPositionModified { get; set; }
|
||||||
@ -405,6 +441,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
public WorkingObject(ObjectPositionInfo positionInfo)
|
public WorkingObject(ObjectPositionInfo positionInfo)
|
||||||
{
|
{
|
||||||
PositionInfo = positionInfo;
|
PositionInfo = positionInfo;
|
||||||
|
RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0;
|
||||||
PositionModified = PositionOriginal = HitObject.Position;
|
PositionModified = PositionOriginal = HitObject.Position;
|
||||||
EndPositionModified = HitObject.EndPosition;
|
EndPositionModified = HitObject.EndPosition;
|
||||||
}
|
}
|
||||||
|
@ -24,22 +24,25 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) } },
|
new object[] { LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) } },
|
||||||
new object[] { LegacyMods.Relax, new[] { typeof(TaikoModRelax) } },
|
new object[] { LegacyMods.Relax, new[] { typeof(TaikoModRelax) } },
|
||||||
new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } },
|
new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } },
|
||||||
new object[] { LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) } },
|
|
||||||
new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } },
|
new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } },
|
||||||
new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } },
|
new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } },
|
||||||
new object[] { LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) } },
|
|
||||||
new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } },
|
new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } },
|
||||||
new object[] { LegacyMods.Cinema, new[] { typeof(TaikoModCinema) } },
|
|
||||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } }
|
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(taiko_mod_mapping))]
|
||||||
|
[TestCase(LegacyMods.Cinema, new[] { typeof(TaikoModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })]
|
||||||
|
[TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })]
|
||||||
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })]
|
||||||
|
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||||
|
|
||||||
[TestCaseSource(nameof(taiko_mod_mapping))]
|
[TestCaseSource(nameof(taiko_mod_mapping))]
|
||||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })]
|
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })]
|
||||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })]
|
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })]
|
||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })]
|
||||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
|
||||||
|
|
||||||
[TestCaseSource(nameof(taiko_mod_mapping))]
|
|
||||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||||
@ -54,9 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values, onlineInfo);
|
||||||
|
|
||||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
@ -119,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
|
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
|
||||||
|
|
||||||
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
|
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
|
||||||
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
|
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.AsNonNull().StartTime - endTime) * 0.9;
|
||||||
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
|
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
|
||||||
|
|
||||||
hitButton = !hitButton;
|
hitButton = !hitButton;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
Actions.AddRange(actions);
|
Actions.AddRange(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
|
if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
|
||||||
if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
|
if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
|
||||||
|
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
string firstRunName;
|
string firstRunName;
|
||||||
|
|
||||||
using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true))
|
using (var host = new CleanRunHeadlessGameHost(bypassCleanupOnDispose: true))
|
||||||
{
|
{
|
||||||
firstRunName = host.Name;
|
firstRunName = host.Name;
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -76,7 +77,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -134,7 +136,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var manager = new ModelManager<BeatmapSetInfo>(storage, realm);
|
var manager = new ModelManager<BeatmapSetInfo>(storage, realm);
|
||||||
|
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@ -160,7 +163,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -187,7 +191,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
await LoadOszIntoStore(importer, realm.Realm);
|
await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -199,7 +203,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -217,7 +221,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -231,7 +235,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? tempPath = TestResources.GetTestBeatmapForImport();
|
string? tempPath = TestResources.GetTestBeatmapForImport();
|
||||||
@ -261,7 +265,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -281,7 +285,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -317,7 +321,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -366,7 +370,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -417,7 +421,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -465,7 +469,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -513,7 +517,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -548,7 +552,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var progressNotification = new ImportProgressNotification();
|
var progressNotification = new ImportProgressNotification();
|
||||||
@ -586,7 +590,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Interlocked.Increment(ref loggedExceptionCount);
|
Interlocked.Increment(ref loggedExceptionCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -607,6 +611,12 @@ namespace osu.Game.Tests.Database
|
|||||||
using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
|
using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
|
||||||
using (var zip = ZipArchive.Open(brokenOsz))
|
using (var zip = ZipArchive.Open(brokenOsz))
|
||||||
{
|
{
|
||||||
|
foreach (var entry in zip.Entries.ToArray())
|
||||||
|
{
|
||||||
|
if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture))
|
||||||
|
zip.RemoveEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
zip.AddEntry("broken.osu", brokenOsu, false);
|
zip.AddEntry("broken.osu", brokenOsu, false);
|
||||||
zip.SaveTo(outStream, CompressionType.Deflate);
|
zip.SaveTo(outStream, CompressionType.Deflate);
|
||||||
}
|
}
|
||||||
@ -627,7 +637,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
checkSingleReferencedFileCount(realm.Realm, 18);
|
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||||
|
|
||||||
Assert.AreEqual(1, loggedExceptionCount);
|
Assert.AreEqual(0, loggedExceptionCount);
|
||||||
|
|
||||||
File.Delete(brokenTempFilename);
|
File.Delete(brokenTempFilename);
|
||||||
});
|
});
|
||||||
@ -638,7 +648,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true);
|
var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true);
|
||||||
@ -665,7 +675,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realmFactory);
|
var importer = new BeatmapImporter(storage, realmFactory);
|
||||||
using var store = new RealmRulesetStore(realmFactory, storage);
|
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||||
@ -697,7 +707,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -724,7 +734,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -750,7 +760,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realm, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var metadata = new BeatmapMetadata
|
var metadata = new BeatmapMetadata
|
||||||
@ -798,7 +808,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -815,7 +825,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -851,7 +861,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -893,7 +903,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -944,7 +954,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Database
|
namespace osu.Game.Tests.Database
|
||||||
{
|
{
|
||||||
@ -27,12 +30,91 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realm, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAsyncWriteAsync()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, _) =>
|
||||||
|
{
|
||||||
|
await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
Assert.That(realm.Run(r => r.All<BeatmapSetInfo>().Count()), Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAsyncWriteWhileBlocking()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
Task writeTask;
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations("testing"))
|
||||||
|
{
|
||||||
|
writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
Thread.Sleep(100);
|
||||||
|
Assert.That(writeTask.IsCompleted, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTask.WaitSafely();
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
Assert.That(realm.Run(r => r.All<BeatmapSetInfo>().Count()), Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAsyncWrite()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely();
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
Assert.That(realm.Run(r => r.All<BeatmapSetInfo>().Count()), Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAsyncWriteAfterDisposal()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
realm.Dispose();
|
||||||
|
Assert.ThrowsAsync<ObjectDisposedException>(() => realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAsyncWriteBeforeDisposal()
|
||||||
|
{
|
||||||
|
ManualResetEventSlim resetEvent = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
var writeTask = realm.WriteAsync(r =>
|
||||||
|
{
|
||||||
|
// ensure that disposal blocks for our execution
|
||||||
|
Assert.That(resetEvent.Wait(100), Is.False);
|
||||||
|
|
||||||
|
r.Add(TestResources.CreateTestBeatmapSetInfo());
|
||||||
|
});
|
||||||
|
|
||||||
|
realm.Dispose();
|
||||||
|
resetEvent.Set();
|
||||||
|
|
||||||
|
writeTask.WaitSafely();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks
|
/// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks
|
||||||
/// due to context fetching semaphores.
|
/// due to context fetching semaphores.
|
||||||
@ -87,7 +169,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.Throws<TimeoutException>(() =>
|
Assert.Throws<TimeoutException>(() =>
|
||||||
{
|
{
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -95,7 +177,7 @@ namespace osu.Game.Tests.Database
|
|||||||
stopThreadedUsage.Set();
|
stopThreadedUsage.Set();
|
||||||
|
|
||||||
// Ensure we can block a second time after the usage has ended.
|
// Ensure we can block a second time after the usage has ended.
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
migratedStorage.DeleteDirectory(string.Empty);
|
migratedStorage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
storage.Migrate(migratedStorage);
|
storage.Migrate(migratedStorage);
|
||||||
}
|
}
|
||||||
@ -59,6 +59,64 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFailedWritePerformsRollback()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
realm.Write(r =>
|
||||||
|
{
|
||||||
|
r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()));
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(realm.Run(r => r.All<BeatmapInfo>()), Is.Empty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFailedNestedWritePerformsRollback()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
realm.Write(r =>
|
||||||
|
{
|
||||||
|
realm.Write(_ =>
|
||||||
|
{
|
||||||
|
r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()));
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(realm.Run(r => r.All<BeatmapInfo>()), Is.Empty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNestedWriteCalls()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
|
var liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
|
||||||
|
realm.Run(r =>
|
||||||
|
r.Write(_ =>
|
||||||
|
r.Write(_ =>
|
||||||
|
r.Add(beatmap)))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAccessAfterAttach()
|
public void TestAccessAfterAttach()
|
||||||
{
|
{
|
||||||
@ -91,6 +149,25 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTransactionRolledBackOnException()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
|
realm.Run(r => r.Write(_ => r.Add(beatmap)));
|
||||||
|
|
||||||
|
var liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => throw new InvalidOperationException()));
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
|
||||||
|
liveBeatmap.PerformWrite(l => l.Hidden = true);
|
||||||
|
Assert.IsTrue(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScopedReadWithoutContext()
|
public void TestScopedReadWithoutContext()
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -84,11 +83,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
// Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`.
|
realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely();
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
|
||||||
}).WaitSafely();
|
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
@ -141,7 +136,7 @@ namespace osu.Game.Tests.Database
|
|||||||
resolvedItems = null;
|
resolvedItems = null;
|
||||||
lastChanges = null;
|
lastChanges = null;
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
Assert.That(resolvedItems, Is.Empty);
|
Assert.That(resolvedItems, Is.Empty);
|
||||||
|
|
||||||
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
@ -159,7 +154,7 @@ namespace osu.Game.Tests.Database
|
|||||||
testEventsArriving(false);
|
testEventsArriving(false);
|
||||||
|
|
||||||
// And make sure even after another context loss we don't get firings.
|
// And make sure even after another context loss we don't get firings.
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
Assert.That(resolvedItems, Is.Null);
|
Assert.That(resolvedItems, Is.Null);
|
||||||
|
|
||||||
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
@ -217,7 +212,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.That(beatmapSetInfo, Is.Not.Null);
|
Assert.That(beatmapSetInfo, Is.Not.Null);
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
// custom disposal action fired when context lost.
|
// custom disposal action fired when context lost.
|
||||||
Assert.That(beatmapSetInfo, Is.Null);
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
@ -231,7 +226,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.That(beatmapSetInfo, Is.Null);
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
Assert.That(beatmapSetInfo, Is.Null);
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
realm.Run(r => r.Refresh());
|
||||||
@ -256,7 +251,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.That(receivedValue, Is.Not.Null);
|
Assert.That(receivedValue, Is.Not.Null);
|
||||||
receivedValue = null;
|
receivedValue = null;
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +262,7 @@ namespace osu.Game.Tests.Database
|
|||||||
subscription.Dispose();
|
subscription.Dispose();
|
||||||
receivedValue = null;
|
receivedValue = null;
|
||||||
|
|
||||||
using (realm.BlockAllOperations())
|
using (realm.BlockAllOperations("testing"))
|
||||||
Assert.That(receivedValue, Is.Null);
|
Assert.That(receivedValue, Is.Null);
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
realm.Run(r => r.Refresh());
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -78,6 +77,16 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
|
/*
|
||||||
|
* Fail rate around 0.15%
|
||||||
|
*
|
||||||
|
* TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : gameplay clock time = 2500
|
||||||
|
* --TearDown
|
||||||
|
* at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
|
||||||
|
* at osu.Framework.Threading.Scheduler.Update()
|
||||||
|
* at osu.Framework.Graphics.Drawable.UpdateSubTree()
|
||||||
|
*/
|
||||||
public void TestSeekPerformsInGameplayTime(
|
public void TestSeekPerformsInGameplayTime(
|
||||||
[Values(1.0, 0.5, 2.0)] double clockRate,
|
[Values(1.0, 0.5, 2.0)] double clockRate,
|
||||||
[Values(0.0, 200.0, -200.0)] double userOffset,
|
[Values(0.0, 200.0, -200.0)] double userOffset,
|
||||||
@ -106,10 +115,10 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
|
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
|
||||||
|
|
||||||
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
|
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
|
||||||
AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f));
|
AddStep("gameplay clock time = 2500", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 2500, 10f));
|
||||||
|
|
||||||
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
|
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
|
||||||
AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f));
|
AddStep("gameplay clock time = 10000", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 10000, 10f));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -4,9 +4,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual
|
namespace osu.Game.Tests.NonVisual
|
||||||
{
|
{
|
||||||
@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
|
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityNoFile()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualitySameHash()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA, "abc");
|
||||||
|
addAudioFile(beatmapSetB, "abc");
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityDifferentHash()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA);
|
||||||
|
addAudioFile(beatmapSetB);
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null)
|
||||||
|
{
|
||||||
|
beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDatabasedWithDatabased()
|
public void TestDatabasedWithDatabased()
|
||||||
{
|
{
|
||||||
|
@ -44,8 +44,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCustomDirectory()
|
public void TestCustomDirectory()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
|
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
|
||||||
@ -63,7 +62,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,8 +69,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSubDirectoryLookup()
|
public void TestSubDirectoryLookup()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
|
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
|
||||||
@ -97,7 +94,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,8 +101,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigration()
|
public void TestMigration()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -173,7 +168,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,9 +175,8 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationBetweenTwoTargets()
|
public void TestMigrationBetweenTwoTargets()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
string customPath2 = prepareCustomPath();
|
using (prepareCustomPath(out string customPath2))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -205,8 +198,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
cleanupPath(customPath2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,8 +205,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationToSameTargetFails()
|
public void TestMigrationToSameTargetFails()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -228,7 +218,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,9 +225,8 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationFailsOnExistingData()
|
public void TestMigrationFailsOnExistingData()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
string customPath2 = prepareCustomPath();
|
using (prepareCustomPath(out string customPath2))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -267,8 +255,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
cleanupPath(customPath2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,8 +262,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationToNestedTargetFails()
|
public void TestMigrationToNestedTargetFails()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -298,7 +283,6 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,8 +290,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationToSeeminglyNestedTarget()
|
public void TestMigrationToSeeminglyNestedTarget()
|
||||||
{
|
{
|
||||||
string customPath = prepareCustomPath();
|
using (prepareCustomPath(out string customPath))
|
||||||
|
|
||||||
using (var host = new CustomTestHeadlessGameHost())
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -328,7 +311,26 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Exit();
|
host.Exit();
|
||||||
cleanupPath(customPath);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackupCreatedOnCorruptRealm()
|
||||||
|
{
|
||||||
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(host.InitialStorage.GetFullPath(OsuGameBase.CLIENT_DATABASE_FILENAME, true), "i am definitely not a realm file");
|
||||||
|
|
||||||
|
LoadOsuIntoHost(host);
|
||||||
|
|
||||||
|
Assert.That(host.InitialStorage.GetFiles(string.Empty, "*_corrupt.realm"), Has.One.Items);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,14 +345,17 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}");
|
private static IDisposable prepareCustomPath(out string path)
|
||||||
|
{
|
||||||
|
path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}");
|
||||||
|
return new InvokeOnDisposal<string>(path, cleanupPath);
|
||||||
|
}
|
||||||
|
|
||||||
private static void cleanupPath(string path)
|
private static void cleanupPath(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Directory.Exists(path))
|
if (Directory.Exists(path)) Directory.Delete(path, true);
|
||||||
Directory.Delete(path, true);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -362,7 +367,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
public Storage InitialStorage { get; }
|
public Storage InitialStorage { get; }
|
||||||
|
|
||||||
public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"")
|
public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"")
|
||||||
: base(callingMethodName: callingMethodName)
|
: base(callingMethodName: callingMethodName, bypassCleanupOnSetup: true)
|
||||||
{
|
{
|
||||||
string defaultStorageLocation = getDefaultLocationFor(this);
|
string defaultStorageLocation = getDefaultLocationFor(this);
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -256,7 +254,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
|
|
||||||
private class CustomFilterCriteria : IRulesetFilterCriteria
|
private class CustomFilterCriteria : IRulesetFilterCriteria
|
||||||
{
|
{
|
||||||
public string CustomValue { get; set; }
|
public string? CustomValue { get; set; }
|
||||||
|
|
||||||
public bool Matches(BeatmapInfo beatmapInfo) => true;
|
public bool Matches(BeatmapInfo beatmapInfo) => true;
|
||||||
|
|
||||||
|
@ -83,14 +83,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
public override event Action<JudgementResult> NewResult
|
public override event Action<JudgementResult> NewResult
|
||||||
{
|
{
|
||||||
add => throw new InvalidOperationException();
|
add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
|
||||||
remove => throw new InvalidOperationException();
|
remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override event Action<JudgementResult> RevertResult
|
public override event Action<JudgementResult> RevertResult
|
||||||
{
|
{
|
||||||
add => throw new InvalidOperationException();
|
add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
|
||||||
remove => throw new InvalidOperationException();
|
remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Playfield Playfield { get; }
|
public override Playfield Playfield { get; }
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
var user = new APIUser { Id = 33 };
|
var user = new APIUser { Id = 33 };
|
||||||
|
|
||||||
AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3);
|
AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3);
|
||||||
AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2);
|
AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -33,10 +33,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
var user = new APIUser { Id = 44 };
|
var user = new APIUser { Id = 44 };
|
||||||
|
|
||||||
AddStep("add user", () => MultiplayerClient.AddUser(user));
|
AddStep("add user", () => MultiplayerClient.AddUser(user));
|
||||||
AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2);
|
AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2);
|
||||||
|
|
||||||
AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3);
|
AddStep("remove user", () => MultiplayerClient.RemoveUser(user));
|
||||||
AddAssert("room has 1 user", () => MultiplayerClient.Room?.Users.Count == 1);
|
AddUntilStep("room has 1 user", () => MultiplayerClient.ClientRoom?.Users.Count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
changeState(6, MultiplayerUserState.WaitingForLoad);
|
changeState(6, MultiplayerUserState.WaitingForLoad);
|
||||||
checkPlayingUserCount(6);
|
checkPlayingUserCount(6);
|
||||||
|
|
||||||
AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.Room?.Users.Last().User).AsNonNull()));
|
AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.ServerRoom?.Users.Last().User).AsNonNull()));
|
||||||
checkPlayingUserCount(5);
|
checkPlayingUserCount(5);
|
||||||
|
|
||||||
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
|
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
|
||||||
@ -103,7 +103,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < userCount; ++i)
|
for (int i = 0; i < userCount; ++i)
|
||||||
{
|
{
|
||||||
int userId = MultiplayerClient.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
|
int userId = MultiplayerClient.ServerRoom?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
|
||||||
MultiplayerClient.ChangeUserState(userId, state);
|
MultiplayerClient.ChangeUserState(userId, state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm)
|
||||||
{
|
{
|
||||||
return new TestBeatmapImporter(this, storage, realm, onlineLookupQueue);
|
return new TestBeatmapImporter(this, storage, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TestBeatmapImporter : BeatmapImporter
|
internal class TestBeatmapImporter : BeatmapImporter
|
||||||
{
|
{
|
||||||
private readonly TestBeatmapManager testBeatmapManager;
|
private readonly TestBeatmapManager testBeatmapManager;
|
||||||
|
|
||||||
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess)
|
||||||
: base(storage, databaseAccess, beatmapOnlineLookupQueue)
|
: base(storage, databaseAccess)
|
||||||
{
|
{
|
||||||
this.testBeatmapManager = testBeatmapManager;
|
this.testBeatmapManager = testBeatmapManager;
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||||
StarRating = diff,
|
StarRating = diff,
|
||||||
Length = length,
|
Length = length,
|
||||||
|
BeatmapSet = beatmapSet,
|
||||||
BPM = bpm,
|
BPM = bpm,
|
||||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||||
Ruleset = rulesetInfo,
|
Ruleset = rulesetInfo,
|
||||||
|
@ -83,20 +83,20 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
#region Cases where imports should match existing
|
#region Cases where imports should match existing
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu =>
|
public Task TestImportTwiceWithSameMetadataAndFilename([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport);
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport);
|
||||||
|
|
||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu =>
|
public Task TestImportTwiceWithNoMetadataSameDownloadFilename([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
|
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport);
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport);
|
||||||
|
|
||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
@ -134,10 +134,10 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu =>
|
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport);
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport);
|
||||||
|
|
||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
||||||
@ -357,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Live<SkinInfo>> loadSkinIntoOsu(OsuGameBase osu, ImportTask import)
|
private async Task<Live<SkinInfo>> loadSkinIntoOsu(OsuGameBase osu, ImportTask import, bool batchImport = false)
|
||||||
{
|
{
|
||||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||||
return await skinManager.Import(import);
|
return await skinManager.Import(import, batchImport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneComposeScreen : EditorClockTestScene
|
public class TestSceneComposeScreen : EditorClockTestScene
|
||||||
{
|
{
|
||||||
private EditorBeatmap editorBeatmap;
|
private EditorBeatmap editorBeatmap = null!;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private EditorClipboard clipboard = new EditorClipboard();
|
private EditorClipboard clipboard = new EditorClipboard();
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -36,12 +35,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
|
|
||||||
|
|
||||||
protected override bool IsolateSavingFromDatabase => false;
|
protected override bool IsolateSavingFromDatabase => false;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||||
|
|
||||||
|
private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty;
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
@ -52,19 +51,19 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null);
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new DummyWorkingBeatmap(Audio, null);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCreateNewBeatmap()
|
public void TestCreateNewBeatmap()
|
||||||
{
|
{
|
||||||
AddStep("save beatmap", () => Editor.Save());
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == false);
|
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID)?.Value.DeletePending == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExitWithoutSave()
|
public void TestExitWithoutSave()
|
||||||
{
|
{
|
||||||
EditorBeatmap editorBeatmap = null;
|
EditorBeatmap editorBeatmap = null!;
|
||||||
|
|
||||||
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
|
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
|
||||||
|
|
||||||
@ -80,12 +79,33 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
|
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
|
||||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true);
|
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull().ID)?.Value.DeletePending == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
|
/*
|
||||||
|
* Fail rate around 1.2%.
|
||||||
|
*
|
||||||
|
* Failing with realm refetch occasionally being null.
|
||||||
|
* My only guess is that the WorkingBeatmap at SetupScreen is dummy instead of the true one.
|
||||||
|
* If it's something else, we have larger issues with realm, but I don't think that's the case.
|
||||||
|
*
|
||||||
|
* at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2)
|
||||||
|
* at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage)
|
||||||
|
* at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage)
|
||||||
|
* at System.Diagnostics.Debug.Fail(String message, String detailMessage)
|
||||||
|
* at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.<performFileOperation>b__0(Realm realm) ModelManager.cs:line 50
|
||||||
|
* at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) RealmExtensions.cs:line 14
|
||||||
|
* at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) ModelManager.cs:line 47
|
||||||
|
* at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) ModelManager.cs:line 37
|
||||||
|
* at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) ResourcesSection.cs:line 115
|
||||||
|
* at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.<TestAddAudioTrack>b__11_0() TestSceneEditorBeatmapCreation.cs:line 101
|
||||||
|
*/
|
||||||
public void TestAddAudioTrack()
|
public void TestAddAudioTrack()
|
||||||
{
|
{
|
||||||
|
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||||
|
|
||||||
AddAssert("switch track to real track", () =>
|
AddAssert("switch track to real track", () =>
|
||||||
{
|
{
|
||||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||||
@ -95,20 +115,26 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
string extractedFolder = $"{temp}_extracted";
|
string extractedFolder = $"{temp}_extracted";
|
||||||
Directory.CreateDirectory(extractedFolder);
|
Directory.CreateDirectory(extractedFolder);
|
||||||
|
|
||||||
using (var zip = ZipArchive.Open(temp))
|
try
|
||||||
zip.WriteToDirectory(extractedFolder);
|
{
|
||||||
|
using (var zip = ZipArchive.Open(temp))
|
||||||
|
zip.WriteToDirectory(extractedFolder);
|
||||||
|
|
||||||
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));
|
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));
|
||||||
|
|
||||||
File.Delete(temp);
|
// ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
|
||||||
Directory.Delete(extractedFolder, true);
|
Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");
|
||||||
|
|
||||||
// ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
|
return success;
|
||||||
Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");
|
}
|
||||||
|
finally
|
||||||
return success;
|
{
|
||||||
|
File.Delete(temp);
|
||||||
|
Directory.Delete(extractedFolder, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +164,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("new beatmap persisted", () =>
|
AddAssert("new beatmap persisted", () =>
|
||||||
{
|
{
|
||||||
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName);
|
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName);
|
||||||
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID);
|
||||||
|
|
||||||
return beatmap != null
|
return beatmap != null
|
||||||
&& beatmap.DifficultyName == firstDifficultyName
|
&& beatmap.DifficultyName == firstDifficultyName
|
||||||
@ -157,7 +183,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("wait for created", () =>
|
AddUntilStep("wait for created", () =>
|
||||||
{
|
{
|
||||||
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
return difficultyName != null && difficultyName != firstDifficultyName;
|
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -173,7 +199,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("new beatmap persisted", () =>
|
AddAssert("new beatmap persisted", () =>
|
||||||
{
|
{
|
||||||
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName);
|
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName);
|
||||||
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID);
|
||||||
|
|
||||||
return beatmap != null
|
return beatmap != null
|
||||||
&& beatmap.DifficultyName == secondDifficultyName
|
&& beatmap.DifficultyName == secondDifficultyName
|
||||||
@ -224,7 +250,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("new beatmap persisted", () =>
|
AddAssert("new beatmap persisted", () =>
|
||||||
{
|
{
|
||||||
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName);
|
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName);
|
||||||
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID);
|
||||||
|
|
||||||
return beatmap != null
|
return beatmap != null
|
||||||
&& beatmap.DifficultyName == originalDifficultyName
|
&& beatmap.DifficultyName == originalDifficultyName
|
||||||
@ -240,7 +266,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("wait for created", () =>
|
AddUntilStep("wait for created", () =>
|
||||||
{
|
{
|
||||||
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
return difficultyName != null && difficultyName != originalDifficultyName;
|
return difficultyName != null && difficultyName != originalDifficultyName;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -259,13 +285,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("save beatmap", () => Editor.Save());
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
|
|
||||||
BeatmapInfo refetchedBeatmap = null;
|
BeatmapInfo? refetchedBeatmap = null;
|
||||||
Live<BeatmapSetInfo> refetchedBeatmapSet = null;
|
Live<BeatmapSetInfo>? refetchedBeatmapSet = null;
|
||||||
|
|
||||||
AddStep("refetch from database", () =>
|
AddStep("refetch from database", () =>
|
||||||
{
|
{
|
||||||
refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName);
|
refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName);
|
||||||
refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("new beatmap persisted", () =>
|
AddAssert("new beatmap persisted", () =>
|
||||||
@ -301,7 +327,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("wait for created", () =>
|
AddUntilStep("wait for created", () =>
|
||||||
{
|
{
|
||||||
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
return difficultyName != null && difficultyName != "New Difficulty";
|
return difficultyName != null && difficultyName != "New Difficulty";
|
||||||
});
|
});
|
||||||
AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)");
|
AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)");
|
||||||
@ -337,7 +363,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("wait for created", () =>
|
AddUntilStep("wait for created", () =>
|
||||||
{
|
{
|
||||||
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
return difficultyName != null && difficultyName != duplicate_difficulty_name;
|
return difficultyName != null && difficultyName != duplicate_difficulty_name;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,17 +6,14 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
|
||||||
using osu.Game.Screens.Edit.GameplayTest;
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
@ -38,15 +35,18 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
|
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
AddStep("test gameplay", () =>
|
AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay());
|
||||||
|
|
||||||
|
AddUntilStep("wait for player", () =>
|
||||||
{
|
{
|
||||||
var testGameplayButton = this.ChildrenOfType<TestGameplayButton>().Single();
|
// notifications may fire at almost any inopportune time and cause annoying test failures.
|
||||||
InputManager.MoveMouseTo(testGameplayButton);
|
// relentlessly attempt to dismiss any and all interfering overlays, which includes notifications.
|
||||||
InputManager.Click(MouseButton.Left);
|
// this is theoretically not foolproof, but it's the best that can be done here.
|
||||||
|
Game.CloseAllOverlays();
|
||||||
|
return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded);
|
|
||||||
AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -22,7 +25,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCantExitWithoutSaving()
|
public void TestCantExitWithoutSaving()
|
||||||
{
|
{
|
||||||
|
AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get<IDialogOverlay>()).IsLoaded);
|
||||||
AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10);
|
AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10);
|
||||||
|
AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value);
|
||||||
AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor);
|
AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +44,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
SaveEditor();
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Hash updated", () => !string.IsNullOrEmpty(EditorBeatmap.BeatmapInfo.BeatmapSet?.Hash));
|
||||||
|
|
||||||
AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||||
AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||||
AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||||
@ -130,6 +137,54 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLengthAndStarRatingUpdated()
|
||||||
|
{
|
||||||
|
WorkingBeatmap working = null;
|
||||||
|
double lastStarRating = 0;
|
||||||
|
double lastLength = 0;
|
||||||
|
|
||||||
|
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
|
||||||
|
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("One hitobject placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true));
|
||||||
|
|
||||||
|
AddAssert("Beatmap length is zero", () => working.BeatmapInfo.Length == 0);
|
||||||
|
checkDifficultyIncreased();
|
||||||
|
|
||||||
|
AddStep("Move forward", () => InputManager.Key(Key.Right));
|
||||||
|
AddStep("Place another hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Two hitobjects placed", () => EditorBeatmap.HitObjects.Count == 2);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true));
|
||||||
|
|
||||||
|
checkDifficultyIncreased();
|
||||||
|
checkLengthIncreased();
|
||||||
|
|
||||||
|
void checkLengthIncreased()
|
||||||
|
{
|
||||||
|
AddStep("Beatmap length increased", () =>
|
||||||
|
{
|
||||||
|
Assert.That(working.BeatmapInfo.Length, Is.GreaterThan(lastLength));
|
||||||
|
lastLength = working.BeatmapInfo.Length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkDifficultyIncreased()
|
||||||
|
{
|
||||||
|
AddStep("Beatmap difficulty increased", () =>
|
||||||
|
{
|
||||||
|
Assert.That(working.BeatmapInfo.StarRating, Is.GreaterThan(lastStarRating));
|
||||||
|
lastStarRating = working.BeatmapInfo.StarRating;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExitWithoutSaveFromExistingBeatmap()
|
public void TestExitWithoutSaveFromExistingBeatmap()
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user