mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into classic_drumrolls
This commit is contained in:
commit
a4f3a0d201
@ -1,4 +1,2 @@
|
|||||||
# Normalize all the line endings
|
# Normalize all the line endings
|
||||||
32a74f95a5c80a0ed18e693f13a47522099df5c3
|
32a74f95a5c80a0ed18e693f13a47522099df5c3
|
||||||
# Enabled NRT globally
|
|
||||||
f8830c6850128456266c82de83273204f8b74ac0
|
|
||||||
|
@ -16,3 +16,10 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable
|
|||||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
|
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
|
||||||
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
||||||
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
||||||
|
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
||||||
|
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||||
|
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||||
|
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
||||||
|
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
|
||||||
|
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.
|
||||||
|
M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>9.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
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.616.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.615.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.722.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. -->
|
||||||
|
76
osu.Android/AndroidJoystickSettings.cs
Normal file
76
osu.Android/AndroidJoystickSettings.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Android.Input;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Android
|
||||||
|
{
|
||||||
|
public class AndroidJoystickSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad;
|
||||||
|
|
||||||
|
private readonly AndroidJoystickHandler joystickHandler;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> enabled = new BindableBool(true);
|
||||||
|
|
||||||
|
private SettingsSlider<float> deadzoneSlider = null!;
|
||||||
|
|
||||||
|
private Bindable<float> handlerDeadzone = null!;
|
||||||
|
|
||||||
|
private Bindable<float> localDeadzone = null!;
|
||||||
|
|
||||||
|
public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler)
|
||||||
|
{
|
||||||
|
this.joystickHandler = joystickHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// use local bindable to avoid changing enabled state of game host's bindable.
|
||||||
|
handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy();
|
||||||
|
localDeadzone = handlerDeadzone.GetUnboundCopy();
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = CommonStrings.Enabled,
|
||||||
|
Current = enabled
|
||||||
|
},
|
||||||
|
deadzoneSlider = new SettingsSlider<float>
|
||||||
|
{
|
||||||
|
LabelText = JoystickSettingsStrings.DeadzoneThreshold,
|
||||||
|
KeyboardStep = 0.01f,
|
||||||
|
DisplayAsPercentage = true,
|
||||||
|
Current = localDeadzone,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
enabled.BindTo(joystickHandler.Enabled);
|
||||||
|
enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true);
|
||||||
|
|
||||||
|
handlerDeadzone.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
bool disabled = localDeadzone.Disabled;
|
||||||
|
|
||||||
|
localDeadzone.Disabled = false;
|
||||||
|
localDeadzone.Value = val.NewValue;
|
||||||
|
localDeadzone.Disabled = disabled;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
osu.Android/AndroidMouseSettings.cs
Normal file
97
osu.Android/AndroidMouseSettings.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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 Android.OS;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Android.Input;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
|
|
||||||
|
namespace osu.Android
|
||||||
|
{
|
||||||
|
public class AndroidMouseSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
private readonly AndroidMouseHandler mouseHandler;
|
||||||
|
|
||||||
|
protected override LocalisableString Header => MouseSettingsStrings.Mouse;
|
||||||
|
|
||||||
|
private Bindable<double> handlerSensitivity = null!;
|
||||||
|
|
||||||
|
private Bindable<double> localSensitivity = null!;
|
||||||
|
|
||||||
|
private Bindable<bool> relativeMode = null!;
|
||||||
|
|
||||||
|
public AndroidMouseSettings(AndroidMouseHandler mouseHandler)
|
||||||
|
{
|
||||||
|
this.mouseHandler = mouseHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager osuConfig)
|
||||||
|
{
|
||||||
|
// use local bindable to avoid changing enabled state of game host's bindable.
|
||||||
|
handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy();
|
||||||
|
localSensitivity = handlerSensitivity.GetUnboundCopy();
|
||||||
|
|
||||||
|
relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
|
||||||
|
|
||||||
|
// High precision/pointer capture is only available on Android 8.0 and up
|
||||||
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||||
|
{
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = MouseSettingsStrings.HighPrecisionMouse,
|
||||||
|
TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip,
|
||||||
|
Current = relativeMode,
|
||||||
|
Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" },
|
||||||
|
},
|
||||||
|
new MouseSettings.SensitivitySetting
|
||||||
|
{
|
||||||
|
LabelText = MouseSettingsStrings.CursorSensitivity,
|
||||||
|
Current = localSensitivity,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust,
|
||||||
|
TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip,
|
||||||
|
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel),
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = MouseSettingsStrings.DisableMouseButtons,
|
||||||
|
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true);
|
||||||
|
|
||||||
|
handlerSensitivity.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
bool disabled = localSensitivity.Disabled;
|
||||||
|
|
||||||
|
localSensitivity.Disabled = false;
|
||||||
|
localSensitivity.Value = val.NewValue;
|
||||||
|
localSensitivity.Disabled = disabled;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,11 @@ using System;
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Android.Input;
|
||||||
|
using osu.Framework.Input.Handlers;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
@ -75,10 +79,31 @@ namespace osu.Android
|
|||||||
LoadComponentAsync(new GameplayScreenRotationLocker(), Add);
|
LoadComponentAsync(new GameplayScreenRotationLocker(), Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SetHost(GameHost host)
|
||||||
|
{
|
||||||
|
base.SetHost(host);
|
||||||
|
host.Window.CursorState |= CursorState.Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||||
|
|
||||||
|
public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
|
||||||
|
{
|
||||||
|
switch (handler)
|
||||||
|
{
|
||||||
|
case AndroidMouseHandler mh:
|
||||||
|
return new AndroidMouseSettings(mh);
|
||||||
|
|
||||||
|
case AndroidJoystickHandler jh:
|
||||||
|
return new AndroidJoystickSettings(jh);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return base.CreateSettingsSubsectionFor(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class AndroidBatteryInfo : BatteryInfo
|
private class AndroidBatteryInfo : BatteryInfo
|
||||||
{
|
{
|
||||||
public override double ChargeLevel => Battery.ChargeLevel;
|
public override double ChargeLevel => Battery.ChargeLevel;
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="AndroidJoystickSettings.cs" />
|
||||||
|
<Compile Include="AndroidMouseSettings.cs" />
|
||||||
<Compile Include="GameplayScreenRotationLocker.cs" />
|
<Compile Include="GameplayScreenRotationLocker.cs" />
|
||||||
<Compile Include="OsuGameActivity.cs" />
|
<Compile Include="OsuGameActivity.cs" />
|
||||||
<Compile Include="OsuGameAndroid.cs" />
|
<Compile Include="OsuGameAndroid.cs" />
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Desktop
|
|||||||
client.OnReady += onReady;
|
client.OnReady += onReady;
|
||||||
|
|
||||||
// safety measure for now, until we performance test / improve backoff for failed connections.
|
// safety measure for now, until we performance test / improve backoff for failed connections.
|
||||||
client.OnConnectionFailed += (_, __) => client.Deinitialize();
|
client.OnConnectionFailed += (_, _) => client.Deinitialize();
|
||||||
|
|
||||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
||||||
|
|
||||||
|
@ -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 System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -20,25 +18,36 @@ using osu.Framework;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Desktop.Windows;
|
using osu.Desktop.Windows;
|
||||||
|
using osu.Framework.Input.Handlers;
|
||||||
|
using osu.Framework.Input.Handlers.Joystick;
|
||||||
|
using osu.Framework.Input.Handlers.Mouse;
|
||||||
|
using osu.Framework.Input.Handlers.Tablet;
|
||||||
|
using osu.Framework.Input.Handlers.Touch;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.IPC;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Overlays.Settings.Sections;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
internal class OsuGameDesktop : OsuGame
|
internal class OsuGameDesktop : OsuGame
|
||||||
{
|
{
|
||||||
public OsuGameDesktop(string[] args = null)
|
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
||||||
|
|
||||||
|
public OsuGameDesktop(string[]? args = null)
|
||||||
: base(args)
|
: base(args)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override StableStorage GetStorageForStableInstall()
|
public override StableStorage? GetStorageForStableInstall()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Host is DesktopGameHost desktopHost)
|
if (Host is DesktopGameHost desktopHost)
|
||||||
{
|
{
|
||||||
string stablePath = getStableInstallPath();
|
string? stablePath = getStableInstallPath();
|
||||||
if (!string.IsNullOrEmpty(stablePath))
|
if (!string.IsNullOrEmpty(stablePath))
|
||||||
return new StableStorage(stablePath, desktopHost);
|
return new StableStorage(stablePath, desktopHost);
|
||||||
}
|
}
|
||||||
@ -51,11 +60,11 @@ namespace osu.Desktop
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getStableInstallPath()
|
private string? getStableInstallPath()
|
||||||
{
|
{
|
||||||
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg"));
|
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg"));
|
||||||
|
|
||||||
string stableInstallPath;
|
string? stableInstallPath;
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
@ -83,15 +92,15 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private string getStableInstallPathFromRegistry()
|
private string? getStableInstallPathFromRegistry()
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager()
|
protected override UpdateManager CreateUpdateManager()
|
||||||
{
|
{
|
||||||
string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
|
string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(packageManaged))
|
if (!string.IsNullOrEmpty(packageManaged))
|
||||||
return new NoActionUpdateManager();
|
return new NoActionUpdateManager();
|
||||||
@ -118,6 +127,8 @@ namespace osu.Desktop
|
|||||||
LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
|
LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
|
||||||
|
|
||||||
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
|
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
|
||||||
|
|
||||||
|
osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetHost(GameHost host)
|
public override void SetHost(GameHost host)
|
||||||
@ -134,8 +145,29 @@ namespace osu.Desktop
|
|||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
|
||||||
|
{
|
||||||
|
switch (handler)
|
||||||
|
{
|
||||||
|
case ITabletHandler th:
|
||||||
|
return new TabletSettings(th);
|
||||||
|
|
||||||
|
case MouseHandler mh:
|
||||||
|
return new MouseSettings(mh);
|
||||||
|
|
||||||
|
case JoystickHandler jh:
|
||||||
|
return new JoystickSettings(jh);
|
||||||
|
|
||||||
|
case TouchHandler th:
|
||||||
|
return new InputSection.HandlerSection(th);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return base.CreateSettingsSubsectionFor(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly List<string> importableFiles = new List<string>();
|
private readonly List<string> importableFiles = new List<string>();
|
||||||
private ScheduledDelegate importSchedule;
|
private ScheduledDelegate? importSchedule;
|
||||||
|
|
||||||
private void fileDrop(string[] filePaths)
|
private void fileDrop(string[] filePaths)
|
||||||
{
|
{
|
||||||
@ -168,5 +200,11 @@ namespace osu.Desktop
|
|||||||
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
|
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
osuSchemeLinkIPCChannel?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,10 @@ using osu.Framework;
|
|||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
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
|
||||||
@ -28,7 +30,27 @@ 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))
|
||||||
|
{
|
||||||
|
// If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
|
||||||
|
// disabling it ourselves.
|
||||||
|
// We could also better detect compatibility mode if required:
|
||||||
|
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
|
||||||
|
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.\n"
|
||||||
|
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"
|
||||||
|
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for 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;
|
||||||
@ -65,19 +87,8 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
if (!host.IsPrimaryInstance)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
if (trySendIPCMessage(host, cwd, args))
|
||||||
{
|
|
||||||
var importer = new ArchiveImportIPCChannel(host);
|
|
||||||
|
|
||||||
foreach (string file in args)
|
|
||||||
{
|
|
||||||
Console.WriteLine(@"Importing {0}", file);
|
|
||||||
if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
|
|
||||||
throw new TimeoutException(@"IPC took too long to send");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// we want to allow multiple instances to be started when in debug.
|
// we want to allow multiple instances to be started when in debug.
|
||||||
if (!DebugUtils.IsDebugBuild)
|
if (!DebugUtils.IsDebugBuild)
|
||||||
@ -108,21 +119,49 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1 && args[0].StartsWith(OsuGameBase.OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var osuSchemeLinkHandler = new OsuSchemeLinkIPCChannel(host);
|
||||||
|
if (!osuSchemeLinkHandler.HandleLinkAsync(args[0]).Wait(3000))
|
||||||
|
throw new IPCTimeoutException(osuSchemeLinkHandler.GetType());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
||||||
|
{
|
||||||
|
var importer = new ArchiveImportIPCChannel(host);
|
||||||
|
|
||||||
|
foreach (string file in args)
|
||||||
|
{
|
||||||
|
Console.WriteLine(@"Importing {0}", file);
|
||||||
|
if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
|
||||||
|
throw new IPCTimeoutException(importer.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private static void setupSquirrel()
|
private static void setupSquirrel()
|
||||||
{
|
{
|
||||||
SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) =>
|
SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
|
||||||
{
|
{
|
||||||
tools.CreateShortcutForThisExe();
|
tools.CreateShortcutForThisExe();
|
||||||
tools.CreateUninstallerRegistryEntry();
|
tools.CreateUninstallerRegistryEntry();
|
||||||
}, onAppUpdate: (version, tools) =>
|
}, onAppUpdate: (_, tools) =>
|
||||||
{
|
{
|
||||||
tools.CreateUninstallerRegistryEntry();
|
tools.CreateUninstallerRegistryEntry();
|
||||||
}, onAppUninstall: (version, tools) =>
|
}, onAppUninstall: (_, tools) =>
|
||||||
{
|
{
|
||||||
tools.RemoveShortcutForThisExe();
|
tools.RemoveShortcutForThisExe();
|
||||||
tools.RemoveUninstallerRegistryEntry();
|
tools.RemoveUninstallerRegistryEntry();
|
||||||
}, onEveryRun: (version, tools, firstRun) =>
|
}, onEveryRun: (_, _, _) =>
|
||||||
{
|
{
|
||||||
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
||||||
// causes the right-click context menu to function incorrectly.
|
// causes the right-click context menu to function incorrectly.
|
||||||
|
@ -154,7 +154,7 @@ namespace osu.Desktop.Updater
|
|||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
updateManager.PrepareUpdateAsync()
|
updateManager.PrepareUpdateAsync()
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit()));
|
.ContinueWith(_ => updateManager.Schedule(() => game?.AttemptExit()));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
166
osu.Game.Benchmarks/BenchmarkHitObject.cs
Normal file
166
osu.Game.Benchmarks/BenchmarkHitObject.cs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkHitObject : BenchmarkTest
|
||||||
|
{
|
||||||
|
[Params(1, 100, 1000)]
|
||||||
|
public int Count { get; set; }
|
||||||
|
|
||||||
|
[Params(false, true)]
|
||||||
|
public bool WithBindableAccess { get; set; }
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public HitCircle[] OsuCircle()
|
||||||
|
{
|
||||||
|
var circles = new HitCircle[Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
circles[i] = new HitCircle();
|
||||||
|
|
||||||
|
if (WithBindableAccess)
|
||||||
|
{
|
||||||
|
_ = circles[i].PositionBindable;
|
||||||
|
_ = circles[i].ScaleBindable;
|
||||||
|
_ = circles[i].ComboIndexBindable;
|
||||||
|
_ = circles[i].ComboOffsetBindable;
|
||||||
|
_ = circles[i].StackHeightBindable;
|
||||||
|
_ = circles[i].LastInComboBindable;
|
||||||
|
_ = circles[i].ComboIndexWithOffsetsBindable;
|
||||||
|
_ = circles[i].IndexInCurrentComboBindable;
|
||||||
|
_ = circles[i].SamplesBindable;
|
||||||
|
_ = circles[i].StartTimeBindable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = circles[i].Position;
|
||||||
|
_ = circles[i].Scale;
|
||||||
|
_ = circles[i].ComboIndex;
|
||||||
|
_ = circles[i].ComboOffset;
|
||||||
|
_ = circles[i].StackHeight;
|
||||||
|
_ = circles[i].LastInCombo;
|
||||||
|
_ = circles[i].ComboIndexWithOffsets;
|
||||||
|
_ = circles[i].IndexInCurrentCombo;
|
||||||
|
_ = circles[i].Samples;
|
||||||
|
_ = circles[i].StartTime;
|
||||||
|
_ = circles[i].Position;
|
||||||
|
_ = circles[i].Scale;
|
||||||
|
_ = circles[i].ComboIndex;
|
||||||
|
_ = circles[i].ComboOffset;
|
||||||
|
_ = circles[i].StackHeight;
|
||||||
|
_ = circles[i].LastInCombo;
|
||||||
|
_ = circles[i].ComboIndexWithOffsets;
|
||||||
|
_ = circles[i].IndexInCurrentCombo;
|
||||||
|
_ = circles[i].Samples;
|
||||||
|
_ = circles[i].StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return circles;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public Hit[] TaikoHit()
|
||||||
|
{
|
||||||
|
var hits = new Hit[Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
hits[i] = new Hit();
|
||||||
|
|
||||||
|
if (WithBindableAccess)
|
||||||
|
{
|
||||||
|
_ = hits[i].TypeBindable;
|
||||||
|
_ = hits[i].IsStrongBindable;
|
||||||
|
_ = hits[i].SamplesBindable;
|
||||||
|
_ = hits[i].StartTimeBindable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = hits[i].Type;
|
||||||
|
_ = hits[i].IsStrong;
|
||||||
|
_ = hits[i].Samples;
|
||||||
|
_ = hits[i].StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public Fruit[] CatchFruit()
|
||||||
|
{
|
||||||
|
var fruit = new Fruit[Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
fruit[i] = new Fruit();
|
||||||
|
|
||||||
|
if (WithBindableAccess)
|
||||||
|
{
|
||||||
|
_ = fruit[i].OriginalXBindable;
|
||||||
|
_ = fruit[i].XOffsetBindable;
|
||||||
|
_ = fruit[i].ScaleBindable;
|
||||||
|
_ = fruit[i].ComboIndexBindable;
|
||||||
|
_ = fruit[i].HyperDashBindable;
|
||||||
|
_ = fruit[i].LastInComboBindable;
|
||||||
|
_ = fruit[i].ComboIndexWithOffsetsBindable;
|
||||||
|
_ = fruit[i].IndexInCurrentComboBindable;
|
||||||
|
_ = fruit[i].IndexInBeatmapBindable;
|
||||||
|
_ = fruit[i].SamplesBindable;
|
||||||
|
_ = fruit[i].StartTimeBindable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = fruit[i].OriginalX;
|
||||||
|
_ = fruit[i].XOffset;
|
||||||
|
_ = fruit[i].Scale;
|
||||||
|
_ = fruit[i].ComboIndex;
|
||||||
|
_ = fruit[i].HyperDash;
|
||||||
|
_ = fruit[i].LastInCombo;
|
||||||
|
_ = fruit[i].ComboIndexWithOffsets;
|
||||||
|
_ = fruit[i].IndexInCurrentCombo;
|
||||||
|
_ = fruit[i].IndexInBeatmap;
|
||||||
|
_ = fruit[i].Samples;
|
||||||
|
_ = fruit[i].StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fruit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public Note[] ManiaNote()
|
||||||
|
{
|
||||||
|
var notes = new Note[Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
notes[i] = new Note();
|
||||||
|
|
||||||
|
if (WithBindableAccess)
|
||||||
|
{
|
||||||
|
_ = notes[i].ColumnBindable;
|
||||||
|
_ = notes[i].SamplesBindable;
|
||||||
|
_ = notes[i].StartTimeBindable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = notes[i].Column;
|
||||||
|
_ = notes[i].Samples;
|
||||||
|
_ = notes[i].StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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; }
|
||||||
@ -31,7 +29,7 @@ namespace osu.Game.Benchmarks
|
|||||||
|
|
||||||
realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME);
|
realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME);
|
||||||
|
|
||||||
realm.Run(r =>
|
realm.Run(_ =>
|
||||||
{
|
{
|
||||||
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
|
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
|
||||||
});
|
});
|
||||||
@ -76,7 +74,7 @@ namespace osu.Game.Benchmarks
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
done.Wait();
|
done.Wait(60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
@ -115,7 +113,7 @@ namespace osu.Game.Benchmarks
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
done.Wait();
|
done.Wait(60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
@ -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();
|
||||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
new JuiceStreamPathVertex(20, -5)
|
new JuiceStreamPathVertex(20, -5)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
removeCount = path.RemoveVertices((_, i) => true);
|
removeCount = path.RemoveVertices((_, _) => true);
|
||||||
Assert.That(removeCount, Is.EqualTo(1));
|
Assert.That(removeCount, Is.EqualTo(1));
|
||||||
Assert.That(path.Vertices, Is.EqualTo(new[]
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
{
|
{
|
||||||
|
@ -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", () =>
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
hyperDashCount = 0;
|
hyperDashCount = 0;
|
||||||
|
|
||||||
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
|
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
|
||||||
Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
|
Player.DrawableRuleset.FrameStableComponents.OnUpdate += _ =>
|
||||||
{
|
{
|
||||||
var catcher = Player.ChildrenOfType<Catcher>().FirstOrDefault();
|
var catcher = Player.ChildrenOfType<Catcher>().FirstOrDefault();
|
||||||
|
|
||||||
|
@ -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" />
|
||||||
|
@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
|
protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
|
||||||
|
|
||||||
protected override string ComponentName => Component.ToString().ToLower();
|
protected override string ComponentName => Component.ToString().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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];
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
private float halfCatcherWidth;
|
private float halfCatcherWidth;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
@ -135,15 +135,15 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
switch (BlueprintContainer.CurrentTool)
|
switch (BlueprintContainer.CurrentTool)
|
||||||
{
|
{
|
||||||
case SelectTool _:
|
case SelectTool:
|
||||||
if (EditorBeatmap.SelectedHitObjects.Count == 0)
|
if (EditorBeatmap.SelectedHitObjects.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
|
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
|
||||||
return getLastSnappableHitObject(minTime);
|
return getLastSnappableHitObject(minTime);
|
||||||
|
|
||||||
case FruitCompositionTool _:
|
case FruitCompositionTool:
|
||||||
case JuiceStreamCompositionTool _:
|
case JuiceStreamCompositionTool:
|
||||||
if (!CursorInPlacementArea)
|
if (!CursorInPlacementArea)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
case Droplet droplet:
|
case Droplet droplet:
|
||||||
return droplet is TinyDroplet ? PositionRange.EMPTY : new PositionRange(droplet.OriginalX);
|
return droplet is TinyDroplet ? PositionRange.EMPTY : new PositionRange(droplet.OriginalX);
|
||||||
|
|
||||||
case JuiceStream _:
|
case JuiceStream:
|
||||||
return GetPositionRange(hitObject.NestedHitObjects);
|
return GetPositionRange(hitObject.NestedHitObjects);
|
||||||
|
|
||||||
case BananaShower _:
|
case BananaShower:
|
||||||
// A banana shower occupies the whole screen width.
|
// A banana shower occupies the whole screen width.
|
||||||
return new PositionRange(0, CatchPlayfield.WIDTH);
|
return new PositionRange(0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case BananaShower _:
|
case BananaShower:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case JuiceStream juiceStream:
|
case JuiceStream juiceStream:
|
||||||
|
@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModDoubleTime : ModDoubleTime
|
public class CatchModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModFlashlight : ModFlashlight<CatchHitObject>
|
public class CatchModFlashlight : ModFlashlight<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
|
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
|
|
||||||
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override string Description => @"Play with fading fruits.";
|
public override string Description => @"Play with fading fruits.";
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
|
|
||||||
private const double fade_out_offset_multiplier = 0.6;
|
private const double fade_out_offset_multiplier = 0.6;
|
||||||
private const double fade_out_duration_multiplier = 0.44;
|
private const double fade_out_duration_multiplier = 0.44;
|
||||||
|
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
public const float OBJECT_RADIUS = 64;
|
public const float OBJECT_RADIUS = 64;
|
||||||
|
|
||||||
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
|
private HitObjectProperty<float> originalX;
|
||||||
|
|
||||||
|
public Bindable<float> OriginalXBindable => originalX.Bindable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
@ -31,18 +33,20 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
set => OriginalXBindable.Value = value;
|
set => originalX.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
private HitObjectProperty<float> xOffset;
|
||||||
|
|
||||||
|
public Bindable<float> XOffsetBindable => xOffset.Bindable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A random offset applied to the horizontal position, set by the beatmap processing.
|
/// A random offset applied to the horizontal position, set by the beatmap processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float XOffset
|
public float XOffset
|
||||||
{
|
{
|
||||||
get => XOffsetBindable.Value;
|
get => xOffset.Value;
|
||||||
set => XOffsetBindable.Value = value;
|
set => xOffset.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,8 +58,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float OriginalX
|
public float OriginalX
|
||||||
{
|
{
|
||||||
get => OriginalXBindable.Value;
|
get => originalX.Value;
|
||||||
set => OriginalXBindable.Value = value;
|
set => originalX.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -69,59 +73,71 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public double TimePreempt { get; set; } = 1000;
|
public double TimePreempt { get; set; } = 1000;
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
private HitObjectProperty<int> indexInBeatmap;
|
||||||
|
|
||||||
|
public Bindable<int> IndexInBeatmapBindable => indexInBeatmap.Bindable;
|
||||||
|
|
||||||
public int IndexInBeatmap
|
public int IndexInBeatmap
|
||||||
{
|
{
|
||||||
get => IndexInBeatmapBindable.Value;
|
get => indexInBeatmap.Value;
|
||||||
set => IndexInBeatmapBindable.Value = value;
|
set => indexInBeatmap.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool NewCombo { get; set; }
|
public virtual bool NewCombo { get; set; }
|
||||||
|
|
||||||
public int ComboOffset { get; set; }
|
public int ComboOffset { get; set; }
|
||||||
|
|
||||||
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
private HitObjectProperty<int> indexInCurrentCombo;
|
||||||
|
|
||||||
|
public Bindable<int> IndexInCurrentComboBindable => indexInCurrentCombo.Bindable;
|
||||||
|
|
||||||
public int IndexInCurrentCombo
|
public int IndexInCurrentCombo
|
||||||
{
|
{
|
||||||
get => IndexInCurrentComboBindable.Value;
|
get => indexInCurrentCombo.Value;
|
||||||
set => IndexInCurrentComboBindable.Value = value;
|
set => indexInCurrentCombo.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
|
private HitObjectProperty<int> comboIndex;
|
||||||
|
|
||||||
|
public Bindable<int> ComboIndexBindable => comboIndex.Bindable;
|
||||||
|
|
||||||
public int ComboIndex
|
public int ComboIndex
|
||||||
{
|
{
|
||||||
get => ComboIndexBindable.Value;
|
get => comboIndex.Value;
|
||||||
set => ComboIndexBindable.Value = value;
|
set => comboIndex.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<int> ComboIndexWithOffsetsBindable { get; } = new Bindable<int>();
|
private HitObjectProperty<int> comboIndexWithOffsets;
|
||||||
|
|
||||||
|
public Bindable<int> ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable;
|
||||||
|
|
||||||
public int ComboIndexWithOffsets
|
public int ComboIndexWithOffsets
|
||||||
{
|
{
|
||||||
get => ComboIndexWithOffsetsBindable.Value;
|
get => comboIndexWithOffsets.Value;
|
||||||
set => ComboIndexWithOffsetsBindable.Value = value;
|
set => comboIndexWithOffsets.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
private HitObjectProperty<bool> lastInCombo;
|
||||||
|
|
||||||
|
public Bindable<bool> LastInComboBindable => lastInCombo.Bindable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next fruit starts a new combo. Used for explodey.
|
/// The next fruit starts a new combo. Used for explodey.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool LastInCombo
|
public virtual bool LastInCombo
|
||||||
{
|
{
|
||||||
get => LastInComboBindable.Value;
|
get => lastInCombo.Value;
|
||||||
set => LastInComboBindable.Value = value;
|
set => lastInCombo.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
private HitObjectProperty<float> scale = new HitObjectProperty<float>(1);
|
||||||
|
|
||||||
|
public Bindable<float> ScaleBindable => scale.Bindable;
|
||||||
|
|
||||||
public float Scale
|
public float Scale
|
||||||
{
|
{
|
||||||
get => ScaleBindable.Value;
|
get => scale.Value;
|
||||||
set => ScaleBindable.Value = value;
|
set => scale.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -24,12 +25,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float DistanceToHyperDash { get; set; }
|
public float DistanceToHyperDash { get; set; }
|
||||||
|
|
||||||
public readonly Bindable<bool> HyperDashBindable = new Bindable<bool>();
|
private HitObjectProperty<bool> hyperDash;
|
||||||
|
|
||||||
|
public Bindable<bool> HyperDashBindable => hyperDash.Bindable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this fruit can initiate a hyperdash.
|
/// Whether this fruit can initiate a hyperdash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HyperDash => HyperDashBindable.Value;
|
public bool HyperDash => hyperDash.Value;
|
||||||
|
|
||||||
private CatchHitObject hyperDashTarget;
|
private CatchHitObject hyperDashTarget;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -389,13 +389,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
switch (source)
|
switch (source)
|
||||||
{
|
{
|
||||||
case Fruit _:
|
case Fruit:
|
||||||
return caughtFruitPool.Get();
|
return caughtFruitPool.Get();
|
||||||
|
|
||||||
case Banana _:
|
case Banana:
|
||||||
return caughtBananaPool.Get();
|
return caughtBananaPool.Get();
|
||||||
|
|
||||||
case Droplet _:
|
case Droplet:
|
||||||
return caughtDropletPool.Get();
|
return caughtDropletPool.Get();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -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" />
|
||||||
|
@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
switch (original)
|
switch (original)
|
||||||
{
|
{
|
||||||
case IHasDistance _:
|
case IHasDistance:
|
||||||
{
|
{
|
||||||
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||||
conversion = generator;
|
conversion = generator;
|
||||||
|
@ -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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private readonly bool isForCurrentRuleset;
|
private readonly bool isForCurrentRuleset;
|
||||||
private readonly double originalOverallDifficulty;
|
private readonly double originalOverallDifficulty;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
@ -53,7 +55,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 +148,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;
|
||||||
|
@ -146,56 +146,56 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
switch (mod)
|
switch (mod)
|
||||||
{
|
{
|
||||||
case ManiaModKey1 _:
|
case ManiaModKey1:
|
||||||
value |= LegacyMods.Key1;
|
value |= LegacyMods.Key1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey2 _:
|
case ManiaModKey2:
|
||||||
value |= LegacyMods.Key2;
|
value |= LegacyMods.Key2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey3 _:
|
case ManiaModKey3:
|
||||||
value |= LegacyMods.Key3;
|
value |= LegacyMods.Key3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey4 _:
|
case ManiaModKey4:
|
||||||
value |= LegacyMods.Key4;
|
value |= LegacyMods.Key4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey5 _:
|
case ManiaModKey5:
|
||||||
value |= LegacyMods.Key5;
|
value |= LegacyMods.Key5;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey6 _:
|
case ManiaModKey6:
|
||||||
value |= LegacyMods.Key6;
|
value |= LegacyMods.Key6;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey7 _:
|
case ManiaModKey7:
|
||||||
value |= LegacyMods.Key7;
|
value |= LegacyMods.Key7;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey8 _:
|
case ManiaModKey8:
|
||||||
value |= LegacyMods.Key8;
|
value |= LegacyMods.Key8;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModKey9 _:
|
case ManiaModKey9:
|
||||||
value |= LegacyMods.Key9;
|
value |= LegacyMods.Key9;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModDualStages _:
|
case ManiaModDualStages:
|
||||||
value |= LegacyMods.KeyCoop;
|
value |= LegacyMods.KeyCoop;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModFadeIn _:
|
case ManiaModFadeIn:
|
||||||
value |= LegacyMods.FadeIn;
|
value |= LegacyMods.FadeIn;
|
||||||
value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that.
|
value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModMirror _:
|
case ManiaModMirror:
|
||||||
value |= LegacyMods.Mirror;
|
value |= LegacyMods.Mirror;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaModRandom _:
|
case ManiaModRandom:
|
||||||
value |= LegacyMods.Random;
|
value |= LegacyMods.Random;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
|
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
|
||||||
|
|
||||||
protected override string ComponentName => Component.ToString().ToLower();
|
protected override string ComponentName => Component.ToString().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ManiaSkinComponents
|
public enum ManiaSkinComponents
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
var rng = new Random((int)Seed.Value);
|
var rng = new Random((int)Seed.Value);
|
||||||
|
|
||||||
int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList();
|
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(_ => rng.Next()).ToList();
|
||||||
|
|
||||||
beatmap.HitObjects.OfType<ManiaHitObject>().ForEach(h => h.Column = shuffledColumns[h.Column]);
|
beatmap.HitObjects.OfType<ManiaHitObject>().ForEach(h => h.Column = shuffledColumns[h.Column]);
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,14 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition
|
public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition
|
||||||
{
|
{
|
||||||
public readonly Bindable<int> ColumnBindable = new Bindable<int>();
|
private HitObjectProperty<int> column;
|
||||||
|
|
||||||
|
public Bindable<int> ColumnBindable => column.Bindable;
|
||||||
|
|
||||||
public virtual int Column
|
public virtual int Column
|
||||||
{
|
{
|
||||||
get => ColumnBindable.Value;
|
get => column.Value;
|
||||||
set => ColumnBindable.Value = value;
|
set => column.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
||||||
|
@ -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;
|
||||||
@ -57,11 +56,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
{
|
{
|
||||||
switch (point)
|
switch (point)
|
||||||
{
|
{
|
||||||
case HitPoint _:
|
case HitPoint:
|
||||||
actions.Add(columnActions[point.Column]);
|
actions.Add(columnActions[point.Column]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReleasePoint _:
|
case ReleasePoint:
|
||||||
actions.Remove(columnActions[point.Column]);
|
actions.Remove(columnActions[point.Column]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
skin.Setup(s => s.GetTexture(It.IsAny<string>())).CallBase();
|
skin.Setup(s => s.GetTexture(It.IsAny<string>())).CallBase();
|
||||||
|
|
||||||
skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny<WrapMode>(), It.IsAny<WrapMode>()))
|
skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny<WrapMode>(), It.IsAny<WrapMode>()))
|
||||||
.Returns((string componentName, WrapMode _, WrapMode __) => new Texture(1, 1) { AssetName = componentName });
|
.Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName });
|
||||||
|
|
||||||
Child = new DependencyProvidingContainer
|
Child = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
lastResult = null;
|
lastResult = null;
|
||||||
|
|
||||||
spinner = nextSpinner;
|
spinner = nextSpinner;
|
||||||
spinner.OnNewResult += (o, result) => lastResult = result;
|
spinner.OnNewResult += (_, result) => lastResult = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastResult?.Type == HitResult.Great;
|
return lastResult?.Type == HitResult.Great;
|
||||||
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
spinner = nextSpinner;
|
spinner = nextSpinner;
|
||||||
spinner.OnNewResult += (o, result) => results.Add(result);
|
spinner.OnNewResult += (_, result) => results.Add(result);
|
||||||
|
|
||||||
results.Clear();
|
results.Clear();
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.6972307565739273d, 206, "diffcalc-test")]
|
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.4484754139145539d, 45, "zero-length-sliders")]
|
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(8.9382559208689809d, 206, "diffcalc-test")]
|
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.7548875851757628d, 45, "zero-length-sliders")]
|
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
||||||
|
|
||||||
[TestCase(6.6972307218715166d, 239, "diffcalc-test")]
|
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
|
||||||
[TestCase(1.4484754139145537d, 54, "zero-length-sliders")]
|
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
|
||||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -161,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
=> addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
|
=> addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
|
||||||
|
|
||||||
private Func<double> timeAtRepeat(Func<double> startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex;
|
private Func<double> timeAtRepeat(Func<double> startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex;
|
||||||
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)getSliderStart : getSliderEnd;
|
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? getSliderStart : getSliderEnd;
|
||||||
|
|
||||||
private List<Vector2> getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
private List<Vector2> getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||||
private Vector2 getSliderStart() => getSliderCurve().First();
|
private Vector2 getSliderStart() => getSliderCurve().First();
|
||||||
@ -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" />
|
||||||
|
@ -108,13 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||||
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
||||||
|
|
||||||
// Reward for % distance slowed down compared to previous, paying attention to not award overlap
|
velocityChangeBonus = overlapVelocityBuff * distRatio;
|
||||||
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
|
|
||||||
// do not award overlap
|
|
||||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
|
|
||||||
|
|
||||||
// Choose the largest bonus, multiplied by ratio.
|
|
||||||
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
|
|
||||||
|
|
||||||
// Penalize for rhythm changes.
|
// Penalize for rhythm changes.
|
||||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -33,14 +32,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
// derive strainTime for calculation
|
// derive strainTime for calculation
|
||||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||||
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||||
|
var osuNextObj = (OsuDifficultyHitObject)current.Next(0);
|
||||||
|
|
||||||
double strainTime = osuCurrObj.StrainTime;
|
double strainTime = osuCurrObj.StrainTime;
|
||||||
double greatWindowFull = greatWindow * 2;
|
double greatWindowFull = greatWindow * 2;
|
||||||
double speedWindowRatio = strainTime / greatWindowFull;
|
double doubletapness = 1;
|
||||||
|
|
||||||
// Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
|
// Nerf doubletappable doubles.
|
||||||
if (osuPrevObj != null && strainTime < greatWindowFull && osuPrevObj.StrainTime > strainTime)
|
if (osuNextObj != null)
|
||||||
strainTime = Interpolation.Lerp(osuPrevObj.StrainTime, strainTime, speedWindowRatio);
|
{
|
||||||
|
double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime);
|
||||||
|
double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
|
||||||
|
double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
|
||||||
|
double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
|
||||||
|
double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2);
|
||||||
|
doubletapness = Math.Pow(speedRatio, 1 - windowRatio);
|
||||||
|
}
|
||||||
|
|
||||||
// Cap deltatime to the OD 300 hitwindow.
|
// Cap deltatime to the OD 300 hitwindow.
|
||||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
||||||
@ -55,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||||
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
|
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
|
||||||
|
|
||||||
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
|
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) * doubletapness / strainTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
private double hitWindowGreat;
|
private double hitWindowGreat;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
@ -38,6 +40,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 +78,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)
|
||||||
|
@ -7,6 +7,9 @@ using System;
|
|||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
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 System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -25,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)
|
||||||
{
|
{
|
||||||
@ -37,12 +42,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
currentStrain *= strainDecay(current.DeltaTime);
|
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
|
||||||
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier;
|
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier;
|
||||||
|
|
||||||
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)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public class SliderPlacementBlueprint : PlacementBlueprint
|
public class SliderPlacementBlueprint : PlacementBlueprint
|
||||||
{
|
{
|
||||||
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
private SliderBodyPiece bodyPiece;
|
private SliderBodyPiece bodyPiece;
|
||||||
private HitCirclePiece headCirclePiece;
|
private HitCirclePiece headCirclePiece;
|
||||||
@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
public SliderPlacementBlueprint()
|
public SliderPlacementBlueprint()
|
||||||
: base(new Objects.Slider())
|
: base(new Slider())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -82,7 +83,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
case SliderPlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
var nearestDifficultyPoint = editorBeatmap.HitObjects
|
||||||
|
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)?
|
||||||
|
.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
||||||
|
|
||||||
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
|
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
|
||||||
selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid();
|
||||||
|
|
||||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
switch (BlueprintContainer.CurrentTool)
|
switch (BlueprintContainer.CurrentTool)
|
||||||
{
|
{
|
||||||
case SelectTool _:
|
case SelectTool:
|
||||||
if (!EditorBeatmap.SelectedHitObjects.Any())
|
if (!EditorBeatmap.SelectedHitObjects.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
drawable.ApplyCustomUpdateState += (drawableObject, state) =>
|
drawable.ApplyCustomUpdateState += (drawableObject, _) =>
|
||||||
{
|
{
|
||||||
if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return;
|
if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return;
|
||||||
|
|
||||||
|
@ -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" });
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
|
||||||
|
|
||||||
private DrawableOsuBlinds blinds;
|
private DrawableOsuBlinds blinds;
|
||||||
|
@ -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), typeof(OsuModRepel) }).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;
|
||||||
|
@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModDoubleTime : ModDoubleTime
|
public class OsuModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObject
|
public class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray();
|
||||||
|
|
||||||
private const double default_follow_delay = 120;
|
private const double default_follow_delay = 120;
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
||||||
|
|
||||||
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
switch (drawableObject)
|
switch (drawableObject)
|
||||||
{
|
{
|
||||||
case DrawableSliderTail _:
|
case DrawableSliderTail:
|
||||||
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
|
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
|
||||||
drawableObject.FadeOut(fadeDuration);
|
drawableObject.FadeOut(fadeDuration);
|
||||||
|
|
||||||
@ -165,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case Slider _:
|
case Slider:
|
||||||
return (fadeOutStartTime, longFadeDuration);
|
return (fadeOutStartTime, longFadeDuration);
|
||||||
|
|
||||||
case SliderTick _:
|
case SliderTick:
|
||||||
double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
||||||
return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
|
return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
|
||||||
|
|
||||||
case Spinner _:
|
case Spinner:
|
||||||
return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
|
return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// apply grow effect
|
// apply grow effect
|
||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
case DrawableSliderHead _:
|
case DrawableSliderHead:
|
||||||
case DrawableSliderTail _:
|
case DrawableSliderTail:
|
||||||
// special cases we should *not* be scaling.
|
// special cases we should *not* be scaling.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider _:
|
case DrawableSlider:
|
||||||
case DrawableHitCircle _:
|
case DrawableHitCircle:
|
||||||
{
|
{
|
||||||
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
|
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
|
||||||
drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
|
drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
|
||||||
|
@ -3,11 +3,14 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModPerfect : ModPerfect
|
public class OsuModPerfect : ModPerfect
|
||||||
{
|
{
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user