mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 04:52:57 +08:00
Merge remote-tracking branch 'upstream/master' into fallback-to-skin-combo-colours
This commit is contained in:
commit
a9125f6544
@ -111,9 +111,15 @@ csharp_preserve_single_line_statements = true
|
|||||||
|
|
||||||
#Roslyn language styles
|
#Roslyn language styles
|
||||||
|
|
||||||
|
#Style - this. qualification
|
||||||
|
dotnet_style_qualification_for_field = false:warning
|
||||||
|
dotnet_style_qualification_for_property = false:warning
|
||||||
|
dotnet_style_qualification_for_method = false:warning
|
||||||
|
dotnet_style_qualification_for_event = false:warning
|
||||||
|
|
||||||
#Style - type names
|
#Style - type names
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
||||||
dotnet_style_predefined_type_for_member_access = true:silent
|
dotnet_style_predefined_type_for_member_access = true:warning
|
||||||
csharp_style_var_when_type_is_apparent = true:none
|
csharp_style_var_when_type_is_apparent = true:none
|
||||||
csharp_style_var_for_built_in_types = true:none
|
csharp_style_var_for_built_in_types = true:none
|
||||||
csharp_style_var_elsewhere = true:silent
|
csharp_style_var_elsewhere = true:silent
|
||||||
@ -126,51 +132,57 @@ csharp_preferred_modifier_order = public,private,protected,internal,new,abstract
|
|||||||
# Skipped because roslyn cannot separate +-*/ with << >>
|
# Skipped because roslyn cannot separate +-*/ with << >>
|
||||||
|
|
||||||
#Style - expression bodies
|
#Style - expression bodies
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
csharp_style_expression_bodied_accessors = true:warning
|
||||||
csharp_style_expression_bodied_constructors = false:none
|
csharp_style_expression_bodied_constructors = false:none
|
||||||
csharp_style_expression_bodied_indexers = true:silent
|
csharp_style_expression_bodied_indexers = true:warning
|
||||||
csharp_style_expression_bodied_methods = true:silent
|
csharp_style_expression_bodied_methods = true:silent
|
||||||
csharp_style_expression_bodied_operators = true:silent
|
csharp_style_expression_bodied_operators = true:warning
|
||||||
csharp_style_expression_bodied_properties = true:silent
|
csharp_style_expression_bodied_properties = true:warning
|
||||||
|
csharp_style_expression_bodied_local_functions = true:silent
|
||||||
|
|
||||||
#Style - expression preferences
|
#Style - expression preferences
|
||||||
dotnet_style_object_initializer = true:warning
|
dotnet_style_object_initializer = true:warning
|
||||||
dotnet_style_collection_initializer = true:warning
|
dotnet_style_collection_initializer = true:warning
|
||||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
|
||||||
dotnet_style_prefer_auto_properties = true:silent
|
dotnet_style_prefer_auto_properties = true:warning
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
dotnet_style_prefer_compound_assignment = true:silent
|
dotnet_style_prefer_compound_assignment = true:warning
|
||||||
|
|
||||||
#Style - null/type checks
|
#Style - null/type checks
|
||||||
dotnet_style_coalesce_expression = true:warning
|
dotnet_style_coalesce_expression = true:warning
|
||||||
dotnet_style_null_propagation = true:warning
|
dotnet_style_null_propagation = true:warning
|
||||||
csharp_style_pattern_matching_over_is_with_cast_check = true:silent
|
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
|
||||||
csharp_style_pattern_matching_over_as_with_null_check = true:silent
|
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||||
csharp_style_throw_expression = true:silent
|
csharp_style_throw_expression = true:silent
|
||||||
csharp_style_conditional_delegate_call = true:suggestion
|
csharp_style_conditional_delegate_call = true:warning
|
||||||
|
|
||||||
#Style - unused
|
#Style - unused
|
||||||
|
dotnet_style_readonly_field = true:silent
|
||||||
dotnet_code_quality_unused_parameters = non_public:silent
|
dotnet_code_quality_unused_parameters = non_public:silent
|
||||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
csharp_style_unused_value_assignment_preference = discard_variable:silent
|
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||||
|
|
||||||
#Style - variable declaration
|
#Style - variable declaration
|
||||||
csharp_style_inlined_variable_declaration = true:silent
|
csharp_style_inlined_variable_declaration = true:warning
|
||||||
csharp_style_deconstructed_variable_declaration = true:silent
|
csharp_style_deconstructed_variable_declaration = true:warning
|
||||||
|
|
||||||
#Style - other C# 7.x features
|
#Style - other C# 7.x features
|
||||||
csharp_style_expression_bodied_local_functions = true:silent
|
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:warning
|
dotnet_style_prefer_inferred_tuple_names = true:warning
|
||||||
csharp_prefer_simple_default_expression = true:warning
|
csharp_prefer_simple_default_expression = true:warning
|
||||||
csharp_style_pattern_local_over_anonymous_function = true:silent
|
csharp_style_pattern_local_over_anonymous_function = true:warning
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||||
|
|
||||||
|
#Style - C# 8 features
|
||||||
|
csharp_prefer_static_local_function = true:warning
|
||||||
|
csharp_prefer_simple_using_statement = true:silent
|
||||||
|
csharp_style_prefer_index_operator = false:none
|
||||||
|
csharp_style_prefer_range_operator = false:none
|
||||||
|
csharp_style_prefer_switch_expression = false:none
|
||||||
|
|
||||||
#Supressing roslyn built-in analyzers
|
#Supressing roslyn built-in analyzers
|
||||||
# Suppress: EC112
|
# Suppress: EC112
|
||||||
|
|
||||||
#Field can be readonly
|
|
||||||
dotnet_diagnostic.IDE0044.severity = silent
|
|
||||||
#Private method is unused
|
#Private method is unused
|
||||||
dotnet_diagnostic.IDE0051.severity = silent
|
dotnet_diagnostic.IDE0051.severity = silent
|
||||||
#Private member is unused
|
#Private member is unused
|
||||||
|
6
CodeAnalysis/BannedSymbols.txt
Normal file
6
CodeAnalysis/BannedSymbols.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||||
|
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||||
|
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||||
|
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
||||||
|
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
||||||
|
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
@ -1,7 +1,8 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>7.3</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||||
@ -14,12 +15,21 @@
|
|||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup Label="Code Analysis">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||||
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Documentation">
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
<!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
||||||
This is required due to https://github.com/NuGet/Home/issues/5740 -->
|
This is required due to https://github.com/NuGet/Home/issues/5740 -->
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
<Authors>ppy Pty Ltd</Authors>
|
<Authors>ppy Pty Ltd</Authors>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
|
||||||
|
4
build.ps1 → InspectCode.ps1
Executable file → Normal file
4
build.ps1 → InspectCode.ps1
Executable file → Normal file
@ -22,6 +22,6 @@ if ($Experimental) { $cakeArguments += "-experimental" }
|
|||||||
$cakeArguments += $ScriptArgs
|
$cakeArguments += $ScriptArgs
|
||||||
|
|
||||||
dotnet tool restore
|
dotnet tool restore
|
||||||
dotnet cake ./build/build.cake --bootstrap
|
dotnet cake ./build/InspectCode.cake --bootstrap
|
||||||
dotnet cake ./build/build.cake $cakeArguments
|
dotnet cake ./build/InspectCode.cake $cakeArguments
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
34
README.md
34
README.md
@ -4,13 +4,16 @@
|
|||||||
|
|
||||||
# osu!
|
# osu!
|
||||||
|
|
||||||
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu)
|
||||||
|
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)]()
|
||||||
|
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
|
||||||
|
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
||||||
|
|
||||||
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
|
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
|
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
|
||||||
|
|
||||||
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
|
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
|
||||||
|
|
||||||
@ -20,7 +23,8 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
|
|||||||
|
|
||||||
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
|
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
|
||||||
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
||||||
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
||||||
|
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
|
||||||
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
||||||
|
|
||||||
## Running osu!
|
## Running osu!
|
||||||
@ -55,44 +59,50 @@ git pull
|
|||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
|
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
||||||
|
|
||||||
- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations.
|
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
|
||||||
- Visual Studio Code users must run the `Restore` task before any build attempt.
|
- Visual Studio Code users must run the `Restore` task before any build attempt.
|
||||||
|
|
||||||
You can also build and run osu! from the command-line with a single command:
|
You can also build and run *osu!* from the command-line with a single command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dotnet run --project osu.Desktop
|
dotnet run --project osu.Desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
|
If you are not interested in debugging *osu!*, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
|
||||||
|
|
||||||
If the build fails, try to restore NuGet packages with `dotnet restore`.
|
If the build fails, try to restore NuGet packages with `dotnet restore`.
|
||||||
|
|
||||||
|
_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._
|
||||||
|
|
||||||
### Testing with resource/framework modifications
|
### Testing with resource/framework modifications
|
||||||
|
|
||||||
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
|
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
|
||||||
|
|
||||||
### Code analysis
|
### Code analysis
|
||||||
|
|
||||||
Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
|
||||||
|
|
||||||
|
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
|
||||||
|
|
||||||
|
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
|
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
|
||||||
|
|
||||||
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
|
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
|
||||||
|
|
||||||
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
|
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
|
||||||
|
|
||||||
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
|
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
|
||||||
|
|
||||||
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
|
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
|
||||||
|
|
||||||
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
|
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
|
||||||
|
|
||||||
|
27
appveyor.yml
27
appveyor.yml
@ -1,6 +1,27 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{branch}-{build}'
|
version: '{branch}-{build}'
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2019
|
||||||
test: off
|
dotnet_csproj:
|
||||||
build_script:
|
patch: true
|
||||||
- cmd: PowerShell -Version 2.0 .\build.ps1
|
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
||||||
|
version: '0.0.{build}'
|
||||||
|
cache:
|
||||||
|
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
|
||||||
|
before_build:
|
||||||
|
- ps: dotnet --info # Useful when version mismatch between CI and local
|
||||||
|
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
||||||
|
build:
|
||||||
|
project: osu.sln
|
||||||
|
parallel: true
|
||||||
|
verbosity: minimal
|
||||||
|
publish_nuget: true
|
||||||
|
after_build:
|
||||||
|
- ps: dotnet tool restore
|
||||||
|
- ps: dotnet format --dry-run --check
|
||||||
|
- ps: .\InspectCode.ps1
|
||||||
|
test:
|
||||||
|
assemblies:
|
||||||
|
except:
|
||||||
|
- '**\*Android*'
|
||||||
|
- '**\*iOS*'
|
||||||
|
- 'build\**\*'
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2019
|
||||||
|
dotnet_csproj:
|
||||||
|
patch: true
|
||||||
|
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
||||||
|
version: $(APPVEYOR_REPO_TAG_NAME)
|
||||||
|
before_build:
|
||||||
|
- ps: dotnet --info # Useful when version mismatch between CI and local
|
||||||
|
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
||||||
test: off
|
test: off
|
||||||
skip_non_tags: true
|
skip_non_tags: true
|
||||||
build_script:
|
configuration: Release
|
||||||
- cmd: PowerShell -Version 2.0 .\build.ps1
|
build:
|
||||||
|
project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
|
||||||
|
parallel: true
|
||||||
|
verbosity: minimal
|
||||||
|
publish_nuget: true
|
||||||
deploy:
|
deploy:
|
||||||
- provider: Environment
|
- provider: Environment
|
||||||
name: nuget
|
name: nuget
|
||||||
|
17
build.sh
17
build.sh
@ -1,17 +0,0 @@
|
|||||||
echo "Installing Cake.Tool..."
|
|
||||||
dotnet tool restore
|
|
||||||
|
|
||||||
# Parse arguments.
|
|
||||||
CAKE_ARGUMENTS=()
|
|
||||||
for i in "$@"; do
|
|
||||||
case $1 in
|
|
||||||
-s|--script) SCRIPT="$2"; shift ;;
|
|
||||||
--) shift; CAKE_ARGUMENTS+=("$@"); break ;;
|
|
||||||
*) CAKE_ARGUMENTS+=("$1") ;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Running build script..."
|
|
||||||
dotnet cake ./build/build.cake --bootstrap
|
|
||||||
dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"
|
|
@ -7,45 +7,29 @@ var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
|||||||
// ARGUMENTS
|
// ARGUMENTS
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var target = Argument("target", "Build");
|
var target = Argument("target", "CodeAnalysis");
|
||||||
var configuration = Argument("configuration", "Release");
|
var configuration = Argument("configuration", "Release");
|
||||||
|
|
||||||
var rootDirectory = new DirectoryPath("..");
|
var rootDirectory = new DirectoryPath("..");
|
||||||
var sln = rootDirectory.CombineWithFilePath("osu.sln");
|
var sln = rootDirectory.CombineWithFilePath("osu.sln");
|
||||||
var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj");
|
|
||||||
var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
|
var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// TASKS
|
// TASKS
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Task("Compile")
|
// windows only because both inspectcode and nvika depend on net45
|
||||||
.Does(() => {
|
|
||||||
DotNetCoreBuild(desktopBuilds.FullPath, new DotNetCoreBuildSettings {
|
|
||||||
Configuration = configuration,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Task("Test")
|
|
||||||
.IsDependentOn("Compile")
|
|
||||||
.Does(() => {
|
|
||||||
var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll");
|
|
||||||
|
|
||||||
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
|
|
||||||
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
|
|
||||||
Parallel = true,
|
|
||||||
ToolTimeout = TimeSpan.FromMinutes(10),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// windows only because both inspectcore and nvika depend on net45
|
|
||||||
Task("InspectCode")
|
Task("InspectCode")
|
||||||
.WithCriteria(IsRunningOnWindows())
|
.WithCriteria(IsRunningOnWindows())
|
||||||
.IsDependentOn("Compile")
|
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
InspectCode(desktopSlnf, new InspectCodeSettings {
|
InspectCode(desktopSlnf, new InspectCodeSettings {
|
||||||
CachesHome = "inspectcode",
|
CachesHome = "inspectcode",
|
||||||
OutputFile = "inspectcodereport.xml",
|
OutputFile = "inspectcodereport.xml",
|
||||||
|
ArgumentCustomization = arg => {
|
||||||
|
if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output
|
||||||
|
arg.Append("--verbosity:WARN");
|
||||||
|
return arg;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
|
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
|
||||||
@ -61,13 +45,8 @@ Task("CodeFileSanity")
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("DotnetFormat")
|
Task("CodeAnalysis")
|
||||||
.Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check"));
|
|
||||||
|
|
||||||
Task("Build")
|
|
||||||
.IsDependentOn("CodeFileSanity")
|
.IsDependentOn("CodeFileSanity")
|
||||||
.IsDependentOn("DotnetFormat")
|
.IsDependentOn("InspectCode");
|
||||||
.IsDependentOn("InspectCode")
|
|
||||||
.IsDependentOn("Test");
|
|
||||||
|
|
||||||
RunTarget(target);
|
RunTarget(target);
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.19"
|
"Microsoft.Build.Traversal": "2.0.24"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<LangVersion>8.0</LangVersion>
|
||||||
<OutputPath>bin\$(Configuration)</OutputPath>
|
<OutputPath>bin\$(Configuration)</OutputPath>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
@ -10,7 +11,7 @@
|
|||||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||||
<AndroidApplication>True</AndroidApplication>
|
<AndroidApplication>True</AndroidApplication>
|
||||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||||
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
|
||||||
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
|
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
||||||
@ -53,6 +54,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1112.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1212.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" package="sh.ppy.osulazer" android:installLocation="auto" android:versionName="0.1.0">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" package="sh.ppy.osulazer" android:installLocation="auto" android:versionName="0.1.0">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
@ -112,14 +112,14 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
protected override string LocateBasePath()
|
protected override string LocateBasePath()
|
||||||
{
|
{
|
||||||
bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
|
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
|
||||||
|
|
||||||
string stableInstallPath;
|
string stableInstallPath;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
|
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
|
||||||
|
|
||||||
if (checkExists(stableInstallPath))
|
if (checkExists(stableInstallPath))
|
||||||
return stableInstallPath;
|
return stableInstallPath;
|
||||||
|
11
osu.Desktop/Properties/launchSettings.json
Normal file
11
osu.Desktop/Properties/launchSettings.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"osu! Desktop": {
|
||||||
|
"commandName": "Project"
|
||||||
|
},
|
||||||
|
"osu! Tournament": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "--tournament"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
osu.Desktop/app.manifest
Normal file
20
osu.Desktop/app.manifest
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
<applicationRequestMinimum>
|
||||||
|
<defaultAssemblyRequest permissionSetReference="Custom" />
|
||||||
|
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
||||||
|
</applicationRequestMinimum>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||||
|
<dpiAware>true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</asmv1:assembly>
|
@ -8,6 +8,7 @@
|
|||||||
<Title>osu!lazer</Title>
|
<Title>osu!lazer</Title>
|
||||||
<Product>osu!lazer</Product>
|
<Product>osu!lazer</Product>
|
||||||
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<Version>0.0.0</Version>
|
<Version>0.0.0</Version>
|
||||||
<FileVersion>0.0.0</FileVersion>
|
<FileVersion>0.0.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -23,11 +24,11 @@
|
|||||||
<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="System.IO.Packaging" Version="4.6.0" />
|
<PackageReference Include="System.IO.Packaging" Version="4.7.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch.Tests.Android" android:installLocation="auto">
|
<!-- using a different name because package name cannot contain 'catch' -->
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
|
||||||
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -11,12 +11,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Blue
|
Colour = Color4.Blue
|
||||||
},
|
},
|
||||||
new SpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = "custom"
|
Text = "custom"
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using System;
|
|||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
{
|
{
|
||||||
@ -22,48 +23,44 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
|
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var curveData = obj as IHasCurve;
|
|
||||||
var positionData = obj as IHasXPosition;
|
var positionData = obj as IHasXPosition;
|
||||||
var comboData = obj as IHasCombo;
|
var comboData = obj as IHasCombo;
|
||||||
var endTime = obj as IHasEndTime;
|
|
||||||
var legacyOffset = obj as IHasLegacyLastTickOffset;
|
|
||||||
|
|
||||||
if (curveData != null)
|
switch (obj)
|
||||||
{
|
{
|
||||||
yield return new JuiceStream
|
case IHasCurve curveData:
|
||||||
{
|
return new JuiceStream
|
||||||
StartTime = obj.StartTime,
|
{
|
||||||
Samples = obj.Samples,
|
StartTime = obj.StartTime,
|
||||||
Path = curveData.Path,
|
Samples = obj.Samples,
|
||||||
NodeSamples = curveData.NodeSamples,
|
Path = curveData.Path,
|
||||||
RepeatCount = curveData.RepeatCount,
|
NodeSamples = curveData.NodeSamples,
|
||||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
|
RepeatCount = curveData.RepeatCount,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
};
|
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
|
||||||
}
|
}.Yield();
|
||||||
else if (endTime != null)
|
|
||||||
{
|
case IHasEndTime endTime:
|
||||||
yield return new BananaShower
|
return new BananaShower
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
Duration = endTime.Duration,
|
Duration = endTime.Duration,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
};
|
}.Yield();
|
||||||
}
|
|
||||||
else
|
default:
|
||||||
{
|
return new Fruit
|
||||||
yield return new Fruit
|
{
|
||||||
{
|
StartTime = obj.StartTime,
|
||||||
StartTime = obj.StartTime,
|
Samples = obj.Samples,
|
||||||
Samples = obj.Samples,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
|
||||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
|
}.Yield();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
|
||||||
using osu.Game.Rulesets.Catch.MathUtils;
|
using osu.Game.Rulesets.Catch.MathUtils;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
catchObject.XOffset = 0;
|
catchObject.XOffset = 0;
|
||||||
|
|
||||||
if (catchObject is TinyDroplet)
|
if (catchObject is TinyDroplet)
|
||||||
catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
|
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
|
||||||
else if (catchObject is Droplet)
|
else if (catchObject is Droplet)
|
||||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||||
}
|
}
|
||||||
@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentObject.DistanceToHyperDash = distanceToHyper;
|
currentObject.DistanceToHyperDash = distanceToHyper;
|
||||||
lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
|
lastExcess = Math.Clamp(distanceToHyper, 0, halfCatcherWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastDirection = thisDirection;
|
lastDirection = thisDirection;
|
||||||
|
@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
@ -35,12 +34,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
|
|
||||||
var legacyScore = Score as LegacyScoreInfo;
|
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
|
||||||
|
ticksHit = Score?.GetCount100() ?? 0;
|
||||||
fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect];
|
tinyTicksHit = Score?.GetCount50() ?? 0;
|
||||||
ticksHit = legacyScore?.Count100 ?? 0;
|
tinyTicksMissed = Score?.GetCountKatu() ?? 0;
|
||||||
tinyTicksHit = legacyScore?.Count50 ?? 0;
|
|
||||||
tinyTicksMissed = legacyScore?.CountKatu ?? 0;
|
|
||||||
misses = Score.Statistics[HitResult.Miss];
|
misses = Score.Statistics[HitResult.Miss];
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
// Don't count scores made with supposedly unranked mods
|
||||||
@ -48,55 +45,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// We are heavily relying on aim in catch the beat
|
// We are heavily relying on aim in catch the beat
|
||||||
double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f;
|
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
||||||
|
|
||||||
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
|
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
|
||||||
int numTotalHits = totalComboHits();
|
int numTotalHits = totalComboHits();
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
float lengthBonus =
|
double lengthBonus =
|
||||||
0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) +
|
0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) +
|
||||||
(numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f);
|
(numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0);
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
value *= lengthBonus;
|
value *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
value *= Math.Pow(0.97f, misses);
|
value *= Math.Pow(0.97, misses);
|
||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
float beatmapMaxCombo = Attributes.MaxCombo;
|
if (Attributes.MaxCombo > 0)
|
||||||
if (beatmapMaxCombo > 0)
|
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||||
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
|
||||||
|
|
||||||
float approachRate = (float)Attributes.ApproachRate;
|
double approachRateFactor = 1.0;
|
||||||
float approachRateFactor = 1.0f;
|
if (Attributes.ApproachRate > 9.0)
|
||||||
if (approachRate > 9.0f)
|
approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9
|
||||||
approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
|
else if (Attributes.ApproachRate < 8.0)
|
||||||
else if (approachRate < 8.0f)
|
approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8
|
||||||
approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
|
|
||||||
|
|
||||||
value *= approachRateFactor;
|
value *= approachRateFactor;
|
||||||
|
|
||||||
if (mods.Any(m => m is ModHidden))
|
if (mods.Any(m => m is ModHidden))
|
||||||
// Hiddens gives nothing on max approach rate, and more the lower it is
|
// Hiddens gives nothing on max approach rate, and more the lower it is
|
||||||
value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10
|
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
|
||||||
|
|
||||||
if (mods.Any(m => m is ModFlashlight))
|
if (mods.Any(m => m is ModFlashlight))
|
||||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||||
value *= 1.35f * lengthBonus;
|
value *= 1.35 * lengthBonus;
|
||||||
|
|
||||||
// Scale the aim value with accuracy _slightly_
|
// Scale the aim value with accuracy _slightly_
|
||||||
value *= Math.Pow(accuracy(), 5.5f);
|
value *= Math.Pow(accuracy(), 5.5);
|
||||||
|
|
||||||
// Custom multipliers for NoFail. SpunOut is not applicable.
|
// Custom multipliers for NoFail. SpunOut is not applicable.
|
||||||
if (mods.Any(m => m is ModNoFail))
|
if (mods.Any(m => m is ModNoFail))
|
||||||
value *= 0.90f;
|
value *= 0.90;
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f);
|
private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
|
||||||
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
||||||
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
||||||
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
||||||
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
if (lastPlayerPosition == null)
|
if (lastPlayerPosition == null)
|
||||||
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
|
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
|
||||||
|
|
||||||
float playerPosition = MathHelper.Clamp(
|
float playerPosition = Math.Clamp(
|
||||||
lastPlayerPosition.Value,
|
lastPlayerPosition.Value,
|
||||||
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
|
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
|
||||||
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
|
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
|
||||||
|
@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
|||||||
{
|
{
|
||||||
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
|
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
|
||||||
private const uint int_mask = 0x7FFFFFFF;
|
private const uint int_mask = 0x7FFFFFFF;
|
||||||
private const uint y = 842502087;
|
private const uint y_initial = 842502087;
|
||||||
private const uint z = 3579807591;
|
private const uint z_initial = 3579807591;
|
||||||
private const uint w = 273326509;
|
private const uint w_initial = 273326509;
|
||||||
private uint _x, _y = y, _z = z, _w = w;
|
private uint x, y = y_initial, z = z_initial, w = w_initial;
|
||||||
|
|
||||||
public FastRandom(int seed)
|
public FastRandom(int seed)
|
||||||
{
|
{
|
||||||
_x = (uint)seed;
|
x = (uint)seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FastRandom()
|
public FastRandom()
|
||||||
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
|||||||
/// <returns>The random value.</returns>
|
/// <returns>The random value.</returns>
|
||||||
public uint NextUInt()
|
public uint NextUInt()
|
||||||
{
|
{
|
||||||
uint t = _x ^ (_x << 11);
|
uint t = x ^ (x << 11);
|
||||||
_x = _y;
|
x = y;
|
||||||
_y = _z;
|
y = z;
|
||||||
_z = _w;
|
z = w;
|
||||||
return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
|
return w = w ^ (w >> 19) ^ t ^ (t >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,12 +1,48 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModRelax : ModRelax
|
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override string Description => @"Use the mouse to control the catcher.";
|
public override string Description => @"Use the mouse to control the catcher.";
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||||
|
{
|
||||||
|
private readonly CatcherArea.Catcher catcher;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
|
public MouseInputHelper(CatchPlayfield playfield)
|
||||||
|
{
|
||||||
|
catcher = playfield.CatcherArea.MovableCatcher;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
//disable keyboard controls
|
||||||
|
public bool OnPressed(CatchAction action) => true;
|
||||||
|
public bool OnReleased(CatchAction action) => true;
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||||
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
|
|
||||||
protected override void UpdateStateTransforms(ArmedState state)
|
protected override void UpdateStateTransforms(ArmedState state)
|
||||||
{
|
{
|
||||||
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
var endTime = HitObject.GetEndTime();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(endTime, true))
|
using (BeginAbsoluteSequence(endTime, true))
|
||||||
{
|
{
|
||||||
|
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
|
|
||||||
const float small_pulp = large_pulp_3 / 2;
|
const float small_pulp = large_pulp_3 / 2;
|
||||||
|
|
||||||
Vector2 positionAt(float angle, float distance) => new Vector2(
|
static Vector2 positionAt(float angle, float distance) => new Vector2(
|
||||||
distance * (float)Math.Sin(angle * Math.PI / 180),
|
distance * MathF.Sin(angle * MathF.PI / 180),
|
||||||
distance * (float)Math.Cos(angle * Math.PI / 180));
|
distance * MathF.Cos(angle * MathF.PI / 180));
|
||||||
|
|
||||||
switch (representation)
|
switch (representation)
|
||||||
{
|
{
|
||||||
@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
|
border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 colourForRepresentation(FruitVisualRepresentation representation)
|
private Color4 colourForRepresentation(FruitVisualRepresentation representation)
|
||||||
|
@ -116,12 +116,22 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public double Duration => EndTime - StartTime;
|
public double Duration => EndTime - StartTime;
|
||||||
|
|
||||||
private SliderPath path;
|
private readonly SliderPath path = new SliderPath();
|
||||||
|
|
||||||
public SliderPath Path
|
public SliderPath Path
|
||||||
{
|
{
|
||||||
get => path;
|
get => path;
|
||||||
set => path = value;
|
set
|
||||||
|
{
|
||||||
|
path.ControlPoints.Clear();
|
||||||
|
path.ExpectedDistance.Value = null;
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(value.ControlPoints);
|
||||||
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
@ -2,23 +2,21 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Scoring
|
namespace osu.Game.Rulesets.Catch.Scoring
|
||||||
{
|
{
|
||||||
public class CatchScoreProcessor : ScoreProcessor<CatchHitObject>
|
public class CatchScoreProcessor : ScoreProcessor
|
||||||
{
|
{
|
||||||
public CatchScoreProcessor(DrawableRuleset<CatchHitObject> drawableRuleset)
|
public CatchScoreProcessor(IBeatmap beatmap)
|
||||||
: base(drawableRuleset)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private float hpDrainRate;
|
private float hpDrainRate;
|
||||||
|
|
||||||
protected override void ApplyBeatmap(Beatmap<CatchHitObject> beatmap)
|
protected override void ApplyBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
base.ApplyBeatmap(beatmap);
|
base.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
internal readonly CatcherArea CatcherArea;
|
internal readonly CatcherArea CatcherArea;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
Container explodingFruitContainer;
|
Container explodingFruitContainer;
|
||||||
|
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
var additive = createCatcherSprite();
|
var additive = createCatcherSprite();
|
||||||
|
|
||||||
additive.Anchor = Anchor;
|
additive.Anchor = Anchor;
|
||||||
additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
|
additive.OriginPosition += new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
|
||||||
additive.Position = Position;
|
additive.Position = Position;
|
||||||
additive.Scale = Scale;
|
additive.Scale = Scale;
|
||||||
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
|
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
|
||||||
@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
fruit.Y -= RNG.NextSingle() * diff;
|
fruit.Y -= RNG.NextSingle() * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
|
fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
|
||||||
|
|
||||||
caughtFruit.Add(fruit);
|
caughtFruit.Add(fruit);
|
||||||
}
|
}
|
||||||
@ -377,8 +377,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
double dashModifier = Dashing ? 1 : 0.5;
|
double dashModifier = Dashing ? 1 : 0.5;
|
||||||
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
||||||
|
|
||||||
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
|
UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
|
||||||
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
|
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||||
@ -452,6 +451,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
fruit.LifetimeStart = Time.Current;
|
fruit.LifetimeStart = Time.Current;
|
||||||
fruit.Expire();
|
fruit.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdatePosition(float position)
|
||||||
|
{
|
||||||
|
position = Math.Clamp(position, 0, 1);
|
||||||
|
|
||||||
|
if (position == X)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
||||||
|
X = position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
|
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
|
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
|
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap);
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>catch the fruit. to the beat.</Description>
|
<Description>catch the fruit. to the beat.</Description>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Mania.Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Mania.Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!mania Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!mania Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -9,7 +9,6 @@ using osu.Game.Beatmaps;
|
|||||||
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;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
yield return new ConvertValue
|
yield return new ConvertValue
|
||||||
{
|
{
|
||||||
StartTime = hitObject.StartTime,
|
StartTime = hitObject.StartTime,
|
||||||
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
|
EndTime = hitObject.GetEndTime(),
|
||||||
Column = ((ManiaHitObject)hitObject).Column
|
Column = ((ManiaHitObject)hitObject).Column
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
if (TargetColumns >= 10)
|
if (TargetColumns >= 10)
|
||||||
{
|
{
|
||||||
TargetColumns = TargetColumns / 2;
|
TargetColumns /= 2;
|
||||||
Dual = true;
|
Dual = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
{
|
{
|
||||||
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
|
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
|
||||||
|
|
||||||
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
|
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
|
||||||
Random = new FastRandom(seed);
|
Random = new FastRandom(seed);
|
||||||
|
|
||||||
return base.ConvertBeatmap(original);
|
return base.ConvertBeatmap(original);
|
||||||
@ -156,37 +156,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
/// <returns>The hit objects generated.</returns>
|
/// <returns>The hit objects generated.</returns>
|
||||||
private IEnumerable<ManiaHitObject> generateConverted(HitObject original, IBeatmap originalBeatmap)
|
private IEnumerable<ManiaHitObject> generateConverted(HitObject original, IBeatmap originalBeatmap)
|
||||||
{
|
{
|
||||||
var endTimeData = original as IHasEndTime;
|
|
||||||
var distanceData = original as IHasDistance;
|
|
||||||
var positionData = original as IHasPosition;
|
|
||||||
|
|
||||||
Patterns.PatternGenerator conversion = null;
|
Patterns.PatternGenerator conversion = null;
|
||||||
|
|
||||||
if (distanceData != null)
|
switch (original)
|
||||||
{
|
{
|
||||||
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
case IHasDistance _:
|
||||||
conversion = generator;
|
|
||||||
|
|
||||||
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
|
|
||||||
{
|
{
|
||||||
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||||
computeDensity(time);
|
conversion = generator;
|
||||||
|
|
||||||
|
var positionData = original as IHasPosition;
|
||||||
|
|
||||||
|
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
|
||||||
|
{
|
||||||
|
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
||||||
|
computeDensity(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (endTimeData != null)
|
|
||||||
{
|
|
||||||
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
|
|
||||||
|
|
||||||
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
case IHasEndTime endTimeData:
|
||||||
computeDensity(endTimeData.EndTime);
|
{
|
||||||
}
|
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
|
||||||
else if (positionData != null)
|
|
||||||
{
|
|
||||||
computeDensity(original.StartTime);
|
|
||||||
|
|
||||||
conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
|
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
||||||
|
computeDensity(endTimeData.EndTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
recordNote(original.StartTime, positionData.Position);
|
case IHasPosition positionData:
|
||||||
|
{
|
||||||
|
computeDensity(original.StartTime);
|
||||||
|
|
||||||
|
conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
|
||||||
|
|
||||||
|
recordNote(original.StartTime, positionData.Position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversion == null)
|
if (conversion == null)
|
||||||
@ -219,14 +226,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
private Pattern generate()
|
private Pattern generate()
|
||||||
{
|
{
|
||||||
var endTimeData = HitObject as IHasEndTime;
|
|
||||||
var positionData = HitObject as IHasXPosition;
|
var positionData = HitObject as IHasXPosition;
|
||||||
|
|
||||||
int column = GetColumn(positionData?.X ?? 0);
|
int column = GetColumn(positionData?.X ?? 0);
|
||||||
|
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
if (endTimeData != null)
|
if (HitObject is IHasEndTime endTimeData)
|
||||||
{
|
{
|
||||||
pattern.Add(new HoldNote
|
pattern.Add(new HoldNote
|
||||||
{
|
{
|
||||||
@ -237,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
|
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (positionData != null)
|
else if (HitObject is IHasXPosition)
|
||||||
{
|
{
|
||||||
pattern.Add(new Note
|
pattern.Add(new Note
|
||||||
{
|
{
|
||||||
@ -257,9 +263,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
||||||
{
|
{
|
||||||
var curveData = HitObject as IHasCurve;
|
if (!(HitObject is IHasCurve curveData))
|
||||||
|
|
||||||
if (curveData == null)
|
|
||||||
return HitObject.Samples;
|
return HitObject.Samples;
|
||||||
|
|
||||||
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
|
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
foreach (var obj in originalPattern.HitObjects)
|
foreach (var obj in originalPattern.HitObjects)
|
||||||
{
|
{
|
||||||
if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
|
if (!Precision.AlmostEquals(EndTime, obj.GetEndTime()))
|
||||||
intermediatePattern.Add(obj);
|
intermediatePattern.Add(obj);
|
||||||
else
|
else
|
||||||
endTimePattern.Add(obj);
|
endTimePattern.Add(obj);
|
||||||
@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||||
|
|
||||||
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
||||||
@ -474,9 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
||||||
{
|
{
|
||||||
var curveData = HitObject as IHasCurve;
|
if (!(HitObject is IHasCurve curveData))
|
||||||
|
|
||||||
if (curveData == null)
|
|
||||||
return HitObject.Samples;
|
return HitObject.Samples;
|
||||||
|
|
||||||
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
|
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mania.MathUtils;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||||
{
|
{
|
||||||
@ -88,15 +89,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
public override IEnumerable<Pattern> Generate()
|
public override IEnumerable<Pattern> Generate()
|
||||||
{
|
{
|
||||||
yield return generate();
|
Pattern generateCore()
|
||||||
}
|
|
||||||
|
|
||||||
private Pattern generate()
|
|
||||||
{
|
|
||||||
var pattern = new Pattern();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
var pattern = new Pattern();
|
||||||
|
|
||||||
if (TotalColumns == 1)
|
if (TotalColumns == 1)
|
||||||
{
|
{
|
||||||
addToPattern(pattern, 0);
|
addToPattern(pattern, 0);
|
||||||
@ -168,54 +164,56 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlag(PatternType.KeepSingle))
|
if (convertType.HasFlag(PatternType.KeepSingle))
|
||||||
return pattern = generateRandomNotes(1);
|
return generateRandomNotes(1);
|
||||||
|
|
||||||
if (convertType.HasFlag(PatternType.Mirror))
|
if (convertType.HasFlag(PatternType.Mirror))
|
||||||
{
|
{
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
|
return generateRandomPatternWithMirrored(0.12, 0.17, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
|
return generateRandomPatternWithMirrored(0.12, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
|
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(1, 0.62, 0, 0);
|
return generateRandomPattern(1, 0.62, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
|
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
|
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 2)
|
if (ConversionDifficulty > 2)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.18, 0, 0, 0);
|
return generateRandomPattern(0.18, 0, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(0.45, 0, 0, 0);
|
return generateRandomPattern(0.45, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pattern = generateRandomPattern(0, 0, 0, 0);
|
return generateRandomPattern(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
var p = generateCore();
|
||||||
|
|
||||||
|
foreach (var obj in p.HitObjects)
|
||||||
{
|
{
|
||||||
foreach (var obj in pattern.HitObjects)
|
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||||
{
|
StairType = PatternType.ReverseStair;
|
||||||
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||||
StairType = PatternType.ReverseStair;
|
StairType = PatternType.Stair;
|
||||||
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
|
||||||
StairType = PatternType.Stair;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return p.Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -303,8 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
bool addToCentre;
|
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out var addToCentre);
|
||||||
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
|
|
||||||
|
|
||||||
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
|
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
|
||||||
int nextColumn = GetRandomColumn(upperBound: columnLimit);
|
int nextColumn = GetRandomColumn(upperBound: columnLimit);
|
||||||
@ -384,8 +381,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns>
|
/// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns>
|
||||||
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
|
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
|
||||||
{
|
{
|
||||||
addToCentre = false;
|
|
||||||
|
|
||||||
switch (TotalColumns)
|
switch (TotalColumns)
|
||||||
{
|
{
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -7,7 +7,6 @@ using JetBrains.Annotations;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.MathUtils;
|
using osu.Game.Rulesets.Mania.MathUtils;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||||
{
|
{
|
||||||
@ -54,11 +53,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
if (allowSpecial && TotalColumns == 8)
|
if (allowSpecial && TotalColumns == 8)
|
||||||
{
|
{
|
||||||
const float local_x_divisor = 512f / 7;
|
const float local_x_divisor = 512f / 7;
|
||||||
return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1;
|
return Math.Clamp((int)MathF.Floor(position / local_x_divisor), 0, 6) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float localXDivisor = 512f / TotalColumns;
|
float localXDivisor = 512f / TotalColumns;
|
||||||
return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1);
|
return Math.Clamp((int)MathF.Floor(position / localXDivisor), 0, TotalColumns - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
drainTime = 10000;
|
drainTime = 10000;
|
||||||
|
|
||||||
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
|
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
|
||||||
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
|
conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
|
||||||
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
|
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
|
||||||
|
|
||||||
return conversionDifficulty.Value;
|
return conversionDifficulty.Value;
|
||||||
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <param name="nextColumn">A function to retrieve the next column. If null, a randomisation scheme will be used.</param>
|
/// <param name="nextColumn">A function to retrieve the next column. If null, a randomisation scheme will be used.</param>
|
||||||
/// <param name="validation">A function to perform additional validation checks to determine if a column is a valid candidate for a <see cref="HitObject"/>.</param>
|
/// <param name="validation">A function to perform additional validation checks to determine if a column is a valid candidate for a <see cref="HitObject"/>.</param>
|
||||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns"/> is used.</param>
|
/// <param name="upperBound">The maximum column index. If null, <see cref="Patterns.PatternGenerator.TotalColumns">TotalColumns</see> is used.</param>
|
||||||
/// <param name="patterns">A list of patterns for which the validity of a column should be checked against.
|
/// <param name="patterns">A list of patterns for which the validity of a column should be checked against.
|
||||||
/// A column is not a valid candidate if a <see cref="HitObject"/> occupies the same column in any of the patterns.</param>
|
/// A column is not a valid candidate if a <see cref="HitObject"/> occupies the same column in any of the patterns.</param>
|
||||||
/// <returns>A column which has passed the <paramref name="validation"/> check and for which there are no
|
/// <returns>A column which has passed the <paramref name="validation"/> check and for which there are no
|
||||||
@ -148,9 +147,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func<int, int> nextColumn = null, [InstantHandle] Func<int, bool> validation = null,
|
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func<int, int> nextColumn = null, [InstantHandle] Func<int, bool> validation = null,
|
||||||
params Pattern[] patterns)
|
params Pattern[] patterns)
|
||||||
{
|
{
|
||||||
lowerBound = lowerBound ?? RandomStart;
|
lowerBound ??= RandomStart;
|
||||||
upperBound = upperBound ?? TotalColumns;
|
upperBound ??= TotalColumns;
|
||||||
nextColumn = nextColumn ?? (_ => GetRandomColumn(lowerBound, upperBound));
|
nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound));
|
||||||
|
|
||||||
// Check for the initial column
|
// Check for the initial column
|
||||||
if (isValid(initialColumn))
|
if (isValid(initialColumn))
|
||||||
@ -184,7 +183,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// Returns a random column index in the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
|
/// Returns a random column index in the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns"/> is used.</param>
|
/// <param name="upperBound">The maximum column index. If null, <see cref="Patterns.PatternGenerator.TotalColumns"/> is used.</param>
|
||||||
protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
|
protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
scaledScore = Score.TotalScore;
|
scaledScore = Score.TotalScore;
|
||||||
countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
|
countPerfect = Score.Statistics[HitResult.Perfect];
|
||||||
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
|
countGreat = Score.Statistics[HitResult.Great];
|
||||||
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
|
countGood = Score.Statistics[HitResult.Good];
|
||||||
countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
|
countOk = Score.Statistics[HitResult.Ok];
|
||||||
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
|
countMeh = Score.Statistics[HitResult.Meh];
|
||||||
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
|
countMiss = Score.Statistics[HitResult.Miss];
|
||||||
|
|
||||||
if (mods.Any(m => !m.Ranked))
|
if (mods.Any(m => !m.Ranked))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -9,7 +10,6 @@ using osu.Game.Rulesets.Mania.Objects;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
// Flip the vertical coordinate space when scrolling downwards
|
// Flip the vertical coordinate space when scrolling downwards
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
targetPosition = targetPosition - referenceParent.DrawHeight;
|
targetPosition -= referenceParent.DrawHeight;
|
||||||
|
|
||||||
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
|
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
maxColumn = obj.Column;
|
maxColumn = obj.Column;
|
||||||
}
|
}
|
||||||
|
|
||||||
columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
|
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
|
||||||
|
|
||||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
obj.Column += columnDelta;
|
obj.Column += columnDelta;
|
||||||
|
@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Replays
|
namespace osu.Game.Rulesets.Mania.Replays
|
||||||
@ -84,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
var currentObject = Beatmap.HitObjects[i];
|
var currentObject = Beatmap.HitObjects[i];
|
||||||
var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
|
var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
|
||||||
|
|
||||||
double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime;
|
double endTime = currentObject.GetEndTime();
|
||||||
|
|
||||||
bool canDelayKeyUp = nextObjectInColumn == null ||
|
bool canDelayKeyUp = nextObjectInColumn == null ||
|
||||||
nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
|
nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
|
||||||
|
@ -3,13 +3,11 @@
|
|||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Scoring
|
namespace osu.Game.Rulesets.Mania.Scoring
|
||||||
{
|
{
|
||||||
internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject>
|
internal class ManiaScoreProcessor : ScoreProcessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hit HP multiplier at OD = 0.
|
/// The hit HP multiplier at OD = 0.
|
||||||
@ -51,12 +49,12 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double hpMultiplier = 1;
|
private double hpMultiplier = 1;
|
||||||
|
|
||||||
public ManiaScoreProcessor(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
public ManiaScoreProcessor(IBeatmap beatmap)
|
||||||
: base(drawableRuleset)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyBeatmap(Beatmap<ManiaHitObject> beatmap)
|
protected override void ApplyBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
base.ApplyBeatmap(beatmap);
|
base.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
@ -65,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
|
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SimulateAutoplay(Beatmap<ManiaHitObject> beatmap)
|
protected override void SimulateAutoplay(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap);
|
||||||
|
|
||||||
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
|
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
foreach (var stage in stages)
|
foreach (var stage in stages)
|
||||||
{
|
{
|
||||||
sum = sum + stage.Columns.Count;
|
sum += stage.Columns.Count;
|
||||||
if (sum > column)
|
if (sum > column)
|
||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>smash the keys. to the beat.</Description>
|
<Description>smash the keys. to the beat.</Description>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Osu.Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Osu.Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!standard Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!standard Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
@ -41,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
|
static ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
|
EndTime = obj.GetEndTime(),
|
||||||
X = obj.StackedPosition.X,
|
X = obj.StackedPosition.X,
|
||||||
Y = obj.StackedPosition.Y
|
Y = obj.StackedPosition.Y
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.931145117263422, "diffcalc-test")]
|
[TestCase(6.9311451172608853d, "diffcalc-test")]
|
||||||
[TestCase(1.0736587013228804d, "zero-length-sliders")]
|
[TestCase(1.0736587013228804d, "zero-length-sliders")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||||
{
|
{
|
||||||
positionOffset = positionOffset ?? Vector2.Zero;
|
positionOffset ??= Vector2.Zero;
|
||||||
|
|
||||||
var circle = new HitCircle
|
var circle = new HitCircle
|
||||||
{
|
{
|
||||||
|
@ -16,9 +16,11 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -75,14 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
|
protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin);
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin);
|
||||||
|
|
||||||
public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||||
{
|
{
|
||||||
private readonly ISkinSource skin;
|
private readonly ISkinSource skin;
|
||||||
|
|
||||||
public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
|
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
|
||||||
: base(beatmap, frameBasedClock, audio)
|
: base(beatmap, storyboard, frameBasedClock, audio)
|
||||||
{
|
{
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
}
|
}
|
||||||
@ -124,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (!enabled) return null;
|
if (!enabled) return null;
|
||||||
|
|
||||||
return new SpriteText
|
return new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = identifier,
|
Text = identifier,
|
||||||
Font = OsuFont.Default.With(size: 30),
|
Font = OsuFont.Default.With(size: 30),
|
||||||
|
@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
||||||
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||||
|
|
||||||
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||||
|
|
||||||
bool assertSamples(HitObject hitObject)
|
static bool assertSamples(HitObject hitObject)
|
||||||
{
|
{
|
||||||
return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)
|
return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)
|
||||||
&& hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE);
|
&& hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||||
@ -183,8 +183,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<RepeatPoint>().All(assertSamples));
|
||||||
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||||
|
|
||||||
bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||||
bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
|
|
||||||
|
static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
||||||
@ -379,8 +380,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
var osuObject = judgedObject as DrawableOsuHitObject;
|
if (!(judgedObject is DrawableOsuHitObject osuObject))
|
||||||
if (osuObject == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OsuSpriteText text;
|
OsuSpriteText text;
|
||||||
|
@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
AddStep($"move mouse to control point {index}", () =>
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
{
|
{
|
||||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index];
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
||||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Tests.Visual;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -28,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
{
|
{
|
||||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
track = (TrackVirtualManual)working.Track;
|
track = (TrackVirtualManual)working.Track;
|
||||||
return working;
|
return working;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||||
{
|
{
|
||||||
@ -23,52 +24,48 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
|
|
||||||
protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
|
protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var curveData = original as IHasCurve;
|
|
||||||
var endTimeData = original as IHasEndTime;
|
|
||||||
var positionData = original as IHasPosition;
|
var positionData = original as IHasPosition;
|
||||||
var comboData = original as IHasCombo;
|
var comboData = original as IHasCombo;
|
||||||
var legacyOffset = original as IHasLegacyLastTickOffset;
|
|
||||||
|
|
||||||
if (curveData != null)
|
switch (original)
|
||||||
{
|
{
|
||||||
yield return new Slider
|
case IHasCurve curveData:
|
||||||
{
|
return new Slider
|
||||||
StartTime = original.StartTime,
|
{
|
||||||
Samples = original.Samples,
|
StartTime = original.StartTime,
|
||||||
Path = curveData.Path,
|
Samples = original.Samples,
|
||||||
NodeSamples = curveData.NodeSamples,
|
Path = curveData.Path,
|
||||||
RepeatCount = curveData.RepeatCount,
|
NodeSamples = curveData.NodeSamples,
|
||||||
Position = positionData?.Position ?? Vector2.Zero,
|
RepeatCount = curveData.RepeatCount,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
||||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
|
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||||
};
|
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
|
||||||
}
|
}.Yield();
|
||||||
else if (endTimeData != null)
|
|
||||||
{
|
case IHasEndTime endTimeData:
|
||||||
yield return new Spinner
|
return new Spinner
|
||||||
{
|
{
|
||||||
StartTime = original.StartTime,
|
StartTime = original.StartTime,
|
||||||
Samples = original.Samples,
|
Samples = original.Samples,
|
||||||
EndTime = endTimeData.EndTime,
|
EndTime = endTimeData.EndTime,
|
||||||
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
|
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
};
|
}.Yield();
|
||||||
}
|
|
||||||
else
|
default:
|
||||||
{
|
return new HitCircle
|
||||||
yield return new HitCircle
|
{
|
||||||
{
|
StartTime = original.StartTime,
|
||||||
StartTime = original.StartTime,
|
Samples = original.Samples,
|
||||||
Samples = original.Samples,
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
Position = positionData?.Position ?? Vector2.Zero,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
}.Yield();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
if (objectN is Spinner)
|
if (objectN is Spinner)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
|
double endTime = stackBaseObject.GetEndTime();
|
||||||
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||||
|
|
||||||
if (objectN.StartTime - endTime > stackThreshold)
|
if (objectN.StartTime - endTime > stackThreshold)
|
||||||
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||||
if (objectN is Spinner) continue;
|
if (objectN is Spinner) continue;
|
||||||
|
|
||||||
double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime;
|
double endTime = objectN.GetEndTime();
|
||||||
|
|
||||||
if (objectI.StartTime - endTime > stackThreshold)
|
if (objectI.StartTime - endTime > stackThreshold)
|
||||||
//We are no longer within stacking range of the previous object.
|
//We are no longer within stacking range of the previous object.
|
||||||
@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime;
|
double startTime = currHitObject.GetEndTime();
|
||||||
int sliderStack = 0;
|
int sliderStack = 0;
|
||||||
|
|
||||||
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
|
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
|
||||||
@ -217,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||||
{
|
{
|
||||||
currHitObject.StackHeight++;
|
currHitObject.StackHeight++;
|
||||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
|
startTime = beatmap.HitObjects[j].GetEndTime();
|
||||||
}
|
}
|
||||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
||||||
{
|
{
|
||||||
//Case for sliders - bump notes down and right, rather than up and left.
|
//Case for sliders - bump notes down and right, rather than up and left.
|
||||||
sliderStack++;
|
sliderStack++;
|
||||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
|
startTime = beatmap.HitObjects[j].GetEndTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,32 +45,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
accuracy = Score.Accuracy;
|
accuracy = Score.Accuracy;
|
||||||
scoreMaxCombo = Score.MaxCombo;
|
scoreMaxCombo = Score.MaxCombo;
|
||||||
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
|
countGreat = Score.Statistics[HitResult.Great];
|
||||||
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
|
countGood = Score.Statistics[HitResult.Good];
|
||||||
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
|
countMeh = Score.Statistics[HitResult.Meh];
|
||||||
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
|
countMiss = Score.Statistics[HitResult.Miss];
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
// Don't count scores made with supposedly unranked mods
|
||||||
if (mods.Any(m => !m.Ranked))
|
if (mods.Any(m => !m.Ranked))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Custom multipliers for NoFail and SpunOut.
|
// Custom multipliers for NoFail and SpunOut.
|
||||||
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModNoFail))
|
if (mods.Any(m => m is OsuModNoFail))
|
||||||
multiplier *= 0.90f;
|
multiplier *= 0.90;
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModSpunOut))
|
if (mods.Any(m => m is OsuModSpunOut))
|
||||||
multiplier *= 0.95f;
|
multiplier *= 0.95;
|
||||||
|
|
||||||
double aimValue = computeAimValue();
|
double aimValue = computeAimValue();
|
||||||
double speedValue = computeSpeedValue();
|
double speedValue = computeSpeedValue();
|
||||||
double accuracyValue = computeAccuracyValue();
|
double accuracyValue = computeAccuracyValue();
|
||||||
double totalValue =
|
double totalValue =
|
||||||
Math.Pow(
|
Math.Pow(
|
||||||
Math.Pow(aimValue, 1.1f) +
|
Math.Pow(aimValue, 1.1) +
|
||||||
Math.Pow(speedValue, 1.1f) +
|
Math.Pow(speedValue, 1.1) +
|
||||||
Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
|
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
|
||||||
) * multiplier;
|
) * multiplier;
|
||||||
|
|
||||||
if (categoryRatings != null)
|
if (categoryRatings != null)
|
||||||
@ -93,82 +93,82 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (mods.Any(m => m is OsuModTouchDevice))
|
if (mods.Any(m => m is OsuModTouchDevice))
|
||||||
rawAim = Math.Pow(rawAim, 0.8);
|
rawAim = Math.Pow(rawAim, 0.8);
|
||||||
|
|
||||||
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
|
|
||||||
aimValue *= lengthBonus;
|
aimValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
aimValue *= Math.Pow(0.97f, countMiss);
|
aimValue *= Math.Pow(0.97, countMiss);
|
||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
if (beatmapMaxCombo > 0)
|
if (beatmapMaxCombo > 0)
|
||||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
|
||||||
|
|
||||||
double approachRateFactor = 1.0f;
|
double approachRateFactor = 1.0;
|
||||||
|
|
||||||
if (Attributes.ApproachRate > 10.33f)
|
if (Attributes.ApproachRate > 10.33)
|
||||||
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
|
approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
|
||||||
else if (Attributes.ApproachRate < 8.0f)
|
else if (Attributes.ApproachRate < 8.0)
|
||||||
{
|
{
|
||||||
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
|
approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
aimValue *= approachRateFactor;
|
aimValue *= approachRateFactor;
|
||||||
|
|
||||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||||
if (mods.Any(h => h is OsuModHidden))
|
if (mods.Any(h => h is OsuModHidden))
|
||||||
aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
|
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModFlashlight))
|
if (mods.Any(h => h is OsuModFlashlight))
|
||||||
{
|
{
|
||||||
// Apply object-based bonus for flashlight.
|
// Apply object-based bonus for flashlight.
|
||||||
aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
|
aimValue *= 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) +
|
||||||
(totalHits > 200
|
(totalHits > 200
|
||||||
? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
|
? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) +
|
||||||
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f)
|
(totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0)
|
||||||
: 0.0f);
|
: 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the aim value with accuracy _slightly_
|
// Scale the aim value with accuracy _slightly_
|
||||||
aimValue *= 0.5f + accuracy / 2.0f;
|
aimValue *= 0.5 + accuracy / 2.0;
|
||||||
// It is important to also consider accuracy difficulty when doing that
|
// It is important to also consider accuracy difficulty when doing that
|
||||||
aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||||
|
|
||||||
return aimValue;
|
return aimValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeSpeedValue()
|
private double computeSpeedValue()
|
||||||
{
|
{
|
||||||
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
speedValue *= 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
speedValue *= Math.Pow(0.97f, countMiss);
|
speedValue *= Math.Pow(0.97, countMiss);
|
||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
if (beatmapMaxCombo > 0)
|
if (beatmapMaxCombo > 0)
|
||||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
|
||||||
|
|
||||||
double approachRateFactor = 1.0f;
|
double approachRateFactor = 1.0;
|
||||||
if (Attributes.ApproachRate > 10.33f)
|
if (Attributes.ApproachRate > 10.33)
|
||||||
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
|
approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
|
||||||
|
|
||||||
speedValue *= approachRateFactor;
|
speedValue *= approachRateFactor;
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModHidden))
|
if (mods.Any(m => m is OsuModHidden))
|
||||||
speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
|
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||||
|
|
||||||
// Scale the speed value with accuracy _slightly_
|
// Scale the speed value with accuracy _slightly_
|
||||||
speedValue *= 0.02f + accuracy;
|
speedValue *= 0.02 + accuracy;
|
||||||
// It is important to also consider accuracy difficulty when doing that
|
// It is important to also consider accuracy difficulty when doing that
|
||||||
speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
|
speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
|
||||||
|
|
||||||
return speedValue;
|
return speedValue;
|
||||||
}
|
}
|
||||||
@ -190,15 +190,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
// Lots of arbitrary values from testing.
|
// Lots of arbitrary values from testing.
|
||||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||||
double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
|
double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
|
||||||
|
|
||||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||||
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
|
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModHidden))
|
if (mods.Any(m => m is OsuModHidden))
|
||||||
accuracyValue *= 1.08f;
|
accuracyValue *= 1.08;
|
||||||
if (mods.Any(m => m is OsuModFlashlight))
|
if (mods.Any(m => m is OsuModFlashlight))
|
||||||
accuracyValue *= 1.02f;
|
accuracyValue *= 1.02;
|
||||||
|
|
||||||
return accuracyValue;
|
return accuracyValue;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
if (progress % 2 >= 1)
|
if (progress % 2 >= 1)
|
||||||
progress = 1 - progress % 1;
|
progress = 1 - progress % 1;
|
||||||
else
|
else
|
||||||
progress = progress % 1;
|
progress %= 1;
|
||||||
|
|
||||||
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
|
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
|
||||||
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
|
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
|
||||||
|
@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
CornerRadius = Size.X / 2;
|
CornerRadius = Size.X / 2;
|
||||||
|
CornerExponent = 2;
|
||||||
|
|
||||||
InternalChild = new RingPiece();
|
InternalChild = new RingPiece();
|
||||||
}
|
}
|
||||||
|
@ -11,19 +11,21 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointPiece : BlueprintPiece<Slider>
|
public class PathControlPointPiece : BlueprintPiece<Slider>
|
||||||
{
|
{
|
||||||
public Action<int> RequestSelection;
|
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
|
||||||
|
|
||||||
public readonly BindableBool IsSelected = new BindableBool();
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
public readonly int Index;
|
|
||||||
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
@ -36,10 +38,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public PathControlPointPiece(Slider slider, int index)
|
private IBindable<Vector2> sliderPosition;
|
||||||
|
private IBindable<int> pathVersion;
|
||||||
|
|
||||||
|
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
Index = index;
|
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -85,24 +91,100 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.LoadComplete();
|
||||||
|
|
||||||
Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
|
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
||||||
|
sliderPosition.BindValueChanged(_ => updateDisplay());
|
||||||
|
|
||||||
|
pathVersion = slider.Path.Version.GetBoundCopy();
|
||||||
|
pathVersion.BindValueChanged(_ => updateDisplay());
|
||||||
|
|
||||||
|
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
updateMarkerDisplay();
|
updateMarkerDisplay();
|
||||||
updateConnectingPath();
|
updateConnectingPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The connecting path is excluded from positional input
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (RequestSelection == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Button)
|
||||||
|
{
|
||||||
|
case MouseButton.Left:
|
||||||
|
RequestSelection.Invoke(this, e);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MouseButton.Right:
|
||||||
|
if (!IsSelected.Value)
|
||||||
|
RequestSelection.Invoke(this, e);
|
||||||
|
return false; // Allow context menu to show
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left;
|
||||||
|
|
||||||
|
protected override bool OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||||
|
{
|
||||||
|
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||||
|
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
||||||
|
Vector2 movementDelta = snappedPosition - slider.Position;
|
||||||
|
|
||||||
|
slider.Position += movementDelta;
|
||||||
|
slider.StartTime = snappedTime;
|
||||||
|
|
||||||
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
|
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ControlPoint.Position.Value += e.Delta;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragEnd(DragEndEvent e) => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the state of the circular control point marker.
|
/// Updates the state of the circular control point marker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void updateMarkerDisplay()
|
private void updateMarkerDisplay()
|
||||||
{
|
{
|
||||||
|
Position = slider.StackedPosition + ControlPoint.Position.Value;
|
||||||
|
|
||||||
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
|
||||||
if (IsHovered || IsSelected.Value)
|
if (IsHovered || IsSelected.Value)
|
||||||
colour = Color4.White;
|
colour = Color4.White;
|
||||||
marker.Colour = colour;
|
marker.Colour = colour;
|
||||||
@ -115,72 +197,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
if (Index != slider.Path.ControlPoints.Length - 1)
|
int index = slider.Path.ControlPoints.IndexOf(ControlPoint);
|
||||||
|
|
||||||
|
if (index == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (++index != slider.Path.ControlPoints.Count)
|
||||||
{
|
{
|
||||||
path.AddVertex(Vector2.Zero);
|
path.AddVertex(Vector2.Zero);
|
||||||
path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
|
path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The connecting path is excluded from positional input
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
if (RequestSelection != null)
|
|
||||||
{
|
|
||||||
RequestSelection.Invoke(Index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
|
||||||
|
|
||||||
protected override bool OnDrag(DragEvent e)
|
|
||||||
{
|
|
||||||
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
|
||||||
|
|
||||||
if (Index == 0)
|
|
||||||
{
|
|
||||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
|
||||||
Vector2 movementDelta = snappedPosition - slider.Position;
|
|
||||||
|
|
||||||
slider.Position += movementDelta;
|
|
||||||
slider.StartTime = snappedTime;
|
|
||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
|
||||||
for (int i = 1; i < newControlPoints.Length; i++)
|
|
||||||
newControlPoints[i] -= movementDelta;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newControlPoints[Index] += e.Delta;
|
|
||||||
|
|
||||||
if (isSegmentSeparatorWithNext)
|
|
||||||
newControlPoints[Index + 1] = newControlPoints[Index];
|
|
||||||
|
|
||||||
if (isSegmentSeparatorWithPrevious)
|
|
||||||
newControlPoints[Index - 1] = newControlPoints[Index];
|
|
||||||
|
|
||||||
ControlPointsChanged?.Invoke(newControlPoints);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnDragEnd(DragEndEvent e) => true;
|
|
||||||
|
|
||||||
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
|
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,30 +3,37 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using System.Linq;
|
||||||
|
using Humanizer;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osuTK.Input;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
|
||||||
|
|
||||||
internal readonly Container<PathControlPointPiece> Pieces;
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
private IBindableList<PathControlPoint> controlPoints;
|
||||||
private IPlacementHandler placementHandler { get; set; }
|
|
||||||
|
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
{
|
{
|
||||||
@ -43,45 +50,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
|
||||||
|
controlPoints = slider.Path.ControlPoints.GetBoundCopy();
|
||||||
|
controlPoints.ItemsAdded += addControlPoints;
|
||||||
|
controlPoints.ItemsRemoved += removeControlPoints;
|
||||||
|
|
||||||
|
addControlPoints(controlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
private void addControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
||||||
{
|
{
|
||||||
base.Update();
|
foreach (var point in controlPoints)
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
|
||||||
{
|
{
|
||||||
var piece = new PathControlPointPiece(slider, Pieces.Count)
|
var piece = new PathControlPointPiece(slider, point);
|
||||||
{
|
|
||||||
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (allowSelection)
|
if (allowSelection)
|
||||||
piece.RequestSelection = selectPiece;
|
piece.RequestSelection = selectPiece;
|
||||||
|
|
||||||
Pieces.Add(piece);
|
Pieces.Add(piece);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
private void removeControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
||||||
Pieces.Remove(Pieces[Pieces.Count - 1]);
|
{
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
Pieces.RemoveAll(p => p.ControlPoint == point);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
foreach (var piece in Pieces)
|
foreach (var piece in Pieces)
|
||||||
piece.IsSelected.Value = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectPiece(int index)
|
|
||||||
{
|
|
||||||
if (inputManager.CurrentState.Keyboard.ControlPressed)
|
|
||||||
Pieces[index].IsSelected.Toggle();
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
foreach (var piece in Pieces)
|
piece.IsSelected.Value = false;
|
||||||
piece.IsSelected.Value = piece.Index == index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(PlatformAction action)
|
public bool OnPressed(PlatformAction action)
|
||||||
@ -89,45 +92,106 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
switch (action.ActionMethod)
|
switch (action.ActionMethod)
|
||||||
{
|
{
|
||||||
case PlatformActionMethod.Delete:
|
case PlatformActionMethod.Delete:
|
||||||
var newControlPoints = new List<Vector2>();
|
return deleteSelected();
|
||||||
|
|
||||||
foreach (var piece in Pieces)
|
|
||||||
{
|
|
||||||
if (!piece.IsSelected.Value)
|
|
||||||
newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that there are any points to be deleted
|
|
||||||
if (newControlPoints.Count == slider.Path.ControlPoints.Length)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// If there are 0 remaining control points, treat the slider as being deleted
|
|
||||||
if (newControlPoints.Count == 0)
|
|
||||||
{
|
|
||||||
placementHandler?.Delete(slider);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make control points relative
|
|
||||||
Vector2 first = newControlPoints[0];
|
|
||||||
for (int i = 0; i < newControlPoints.Count; i++)
|
|
||||||
newControlPoints[i] = newControlPoints[i] - first;
|
|
||||||
|
|
||||||
// The slider's position defines the position of the first control point, and all further control points are relative to that point
|
|
||||||
slider.Position = slider.Position + first;
|
|
||||||
|
|
||||||
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
|
||||||
foreach (var piece in Pieces)
|
|
||||||
piece.IsSelected.Value = false;
|
|
||||||
|
|
||||||
ControlPointsChanged?.Invoke(newControlPoints.ToArray());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
||||||
|
|
||||||
|
private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
||||||
|
piece.IsSelected.Toggle();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var p in Pieces)
|
||||||
|
p.IsSelected.Value = p == piece;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool deleteSelected()
|
||||||
|
{
|
||||||
|
List<PathControlPoint> toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
|
||||||
|
|
||||||
|
// Ensure that there are any points to be deleted
|
||||||
|
if (toRemove.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
RemoveControlPointsRequested?.Invoke(toRemove);
|
||||||
|
|
||||||
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Pieces.Any(p => p.IsHovered))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
|
||||||
|
int count = selectedPieces.Count;
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<MenuItem> items = new List<MenuItem>();
|
||||||
|
|
||||||
|
if (!selectedPieces.Contains(Pieces[0]))
|
||||||
|
items.Add(createMenuItemForPathType(null));
|
||||||
|
|
||||||
|
// todo: hide/disable items which aren't valid for selected points
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Linear));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.PerfectCurve));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Bezier));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Catmull));
|
||||||
|
|
||||||
|
return new MenuItem[]
|
||||||
|
{
|
||||||
|
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()),
|
||||||
|
new OsuMenuItem("Curve type")
|
||||||
|
{
|
||||||
|
Items = items
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem createMenuItemForPathType(PathType? type)
|
||||||
|
{
|
||||||
|
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||||
|
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
|
||||||
|
|
||||||
|
var item = new PathTypeMenuItem(type, () =>
|
||||||
|
{
|
||||||
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
|
p.ControlPoint.Type.Value = type;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countOfState == totalCount)
|
||||||
|
item.State.Value = TernaryState.True;
|
||||||
|
else if (countOfState > 0)
|
||||||
|
item.State.Value = TernaryState.Indeterminate;
|
||||||
|
else
|
||||||
|
item.State.Value = TernaryState.False;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PathTypeMenuItem : TernaryStateMenuItem
|
||||||
|
{
|
||||||
|
public PathTypeMenuItem(PathType? type, Action action)
|
||||||
|
: base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TernaryState changeState(TernaryState state) => TernaryState.True;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -27,11 +24,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private HitCirclePiece headCirclePiece;
|
private HitCirclePiece headCirclePiece;
|
||||||
private HitCirclePiece tailCirclePiece;
|
private HitCirclePiece tailCirclePiece;
|
||||||
|
|
||||||
private readonly List<Segment> segments = new List<Segment>();
|
|
||||||
private Vector2 cursor;
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
private PlacementState state;
|
private PlacementState state;
|
||||||
|
private PathControlPoint segmentStart;
|
||||||
|
private PathControlPoint cursor;
|
||||||
|
private int currentSegmentLength;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
@ -40,7 +38,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
: base(new Objects.Slider())
|
: base(new Objects.Slider())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
segments.Add(new Segment(Vector2.Zero));
|
|
||||||
|
HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear));
|
||||||
|
currentSegmentLength = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
bodyPiece = new SliderBodyPiece(),
|
bodyPiece = new SliderBodyPiece(),
|
||||||
headCirclePiece = new HitCirclePiece(),
|
headCirclePiece = new HitCirclePiece(),
|
||||||
tailCirclePiece = new HitCirclePiece(),
|
tailCirclePiece = new HitCirclePiece(),
|
||||||
new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() },
|
new PathControlPointVisualiser(HitObject, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
@ -72,9 +72,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
|
ensureCursor();
|
||||||
|
|
||||||
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
||||||
// is used instead since snapping control points doesn't make much sense
|
// is used instead since snapping control points doesn't make much sense
|
||||||
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +93,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
segments.Last().ControlPoints.Add(cursor);
|
ensureCursor();
|
||||||
|
|
||||||
|
// Detatch the cursor
|
||||||
|
cursor = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +115,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||||
{
|
{
|
||||||
segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last()));
|
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
|
||||||
|
segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
|
||||||
|
segmentStart.Type.Value = PathType.Linear;
|
||||||
|
|
||||||
|
currentSegmentLength = 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,14 +141,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
updateSlider();
|
updateSlider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePathType()
|
||||||
|
{
|
||||||
|
switch (currentSegmentLength)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
segmentStart.Type.Value = PathType.Linear;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
segmentStart.Type.Value = PathType.PerfectCurve;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
segmentStart.Type.Value = PathType.Bezier;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCursor()
|
||||||
|
{
|
||||||
|
if (cursor == null)
|
||||||
|
{
|
||||||
|
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } });
|
||||||
|
currentSegmentLength++;
|
||||||
|
|
||||||
|
updatePathType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
|
|
||||||
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
|
|
||||||
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
|
||||||
|
|
||||||
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
|
|
||||||
|
|
||||||
bodyPiece.UpdateFrom(HitObject);
|
bodyPiece.UpdateFrom(HitObject);
|
||||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||||
@ -156,15 +190,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
Initial,
|
Initial,
|
||||||
Body,
|
Body,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Segment
|
|
||||||
{
|
|
||||||
public readonly List<Vector2> ControlPoints = new List<Vector2>();
|
|
||||||
|
|
||||||
public Segment(Vector2 offset)
|
|
||||||
{
|
|
||||||
ControlPoints.Add(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -11,10 +12,10 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
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.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IPlacementHandler placementHandler { get; set; }
|
||||||
|
|
||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
@ -40,10 +44,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||||
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
|
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true)
|
||||||
|
{
|
||||||
|
RemoveControlPointsRequested = removeControlPoints
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IBindable<int> pathVersion;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
||||||
|
pathVersion.BindValueChanged(_ => updatePath());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -77,12 +94,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
Debug.Assert(placementControlPointIndex != null);
|
Debug.Assert(placementControlPointIndex != null);
|
||||||
|
|
||||||
Vector2 position = e.MousePosition - HitObject.Position;
|
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position;
|
||||||
|
|
||||||
var controlPoints = HitObject.Path.ControlPoints.ToArray();
|
|
||||||
controlPoints[placementControlPointIndex.Value] = position;
|
|
||||||
|
|
||||||
onNewControlPoints(controlPoints);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -93,19 +105,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BindableList<PathControlPoint> controlPoints => HitObject.Path.ControlPoints;
|
||||||
|
|
||||||
private int addControlPoint(Vector2 position)
|
private int addControlPoint(Vector2 position)
|
||||||
{
|
{
|
||||||
position -= HitObject.Position;
|
position -= HitObject.Position;
|
||||||
|
|
||||||
var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1];
|
|
||||||
HitObject.Path.ControlPoints.CopyTo(controlPoints);
|
|
||||||
|
|
||||||
int insertionIndex = 0;
|
int insertionIndex = 0;
|
||||||
float minDistance = float.MaxValue;
|
float minDistance = float.MaxValue;
|
||||||
|
|
||||||
for (int i = 0; i < controlPoints.Length - 2; i++)
|
for (int i = 0; i < controlPoints.Count - 1; i++)
|
||||||
{
|
{
|
||||||
float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position);
|
float dist = new Line(controlPoints[i].Position.Value, controlPoints[i + 1].Position.Value).DistanceToPoint(position);
|
||||||
|
|
||||||
if (dist < minDistance)
|
if (dist < minDistance)
|
||||||
{
|
{
|
||||||
@ -115,21 +126,45 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
// Move the control points from the insertion index onwards to make room for the insertion
|
||||||
Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1);
|
controlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } });
|
||||||
controlPoints[insertionIndex] = position;
|
|
||||||
|
|
||||||
onNewControlPoints(controlPoints);
|
|
||||||
|
|
||||||
return insertionIndex;
|
return insertionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewControlPoints(Vector2[] controlPoints)
|
private void removeControlPoints(List<PathControlPoint> toRemove)
|
||||||
{
|
{
|
||||||
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
|
// Ensure that there are any points to be deleted
|
||||||
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
if (toRemove.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
|
foreach (var c in toRemove)
|
||||||
|
{
|
||||||
|
// The first control point in the slider must have a type, so take it from the previous "first" one
|
||||||
|
// Todo: Should be handled within SliderPath itself
|
||||||
|
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type.Value == null)
|
||||||
|
controlPoints[1].Type.Value = controlPoints[0].Type.Value;
|
||||||
|
|
||||||
|
controlPoints.Remove(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||||
|
if (controlPoints.Count <= 1)
|
||||||
|
{
|
||||||
|
placementHandler?.Delete(HitObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
|
||||||
|
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
||||||
|
Vector2 first = controlPoints[0].Position.Value;
|
||||||
|
foreach (var c in controlPoints)
|
||||||
|
c.Position.Value -= first;
|
||||||
|
HitObject.Position += first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePath()
|
||||||
|
{
|
||||||
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
UpdateHitObject();
|
UpdateHitObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
{
|
{
|
||||||
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
||||||
: base(hitObject, nextHitObject, hitObject.StackedEndPosition)
|
: base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -13,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier;
|
private float calculateGap(float value) => Math.Clamp(value, 0, target_clamp) * targetBreakMultiplier;
|
||||||
|
|
||||||
// lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve
|
// lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve
|
||||||
private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value;
|
private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var destination = e.MousePosition;
|
var destination = e.MousePosition;
|
||||||
|
|
||||||
FlashlightPosition = Interpolation.ValueAt(
|
FlashlightPosition = Interpolation.ValueAt(
|
||||||
MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
||||||
|
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
@ -22,18 +22,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
|
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
|
||||||
|
|
||||||
var slider = hitObject as Slider;
|
if (!(hitObject is Slider slider))
|
||||||
if (slider == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
|
||||||
var newControlPoints = new Vector2[slider.Path.ControlPoints.Length];
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Length; i++)
|
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
||||||
newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y);
|
|
||||||
|
|
||||||
slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
||||||
|
|
||||||
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
|
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
|
||||||
{
|
{
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
|
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
|
||||||
|
|
||||||
// new duration from completed fade in to end (before fading out)
|
// new duration from completed fade in to end (before fading out)
|
||||||
var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime;
|
var longFadeDuration = h.GetEndTime() - fadeOutStartTime;
|
||||||
|
|
||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
|
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
|
||||||
|
|
||||||
Vector2 originalPosition = drawable.Position;
|
Vector2 originalPosition = drawable.Position;
|
||||||
Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
|
Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance;
|
||||||
|
|
||||||
//the - 1 and + 1 prevents the hit objects to appear in the wrong position.
|
//the - 1 and + 1 prevents the hit objects to appear in the wrong position.
|
||||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
|
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
|
||||||
|
@ -25,11 +25,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container
|
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
CornerRadius = width / 2,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
|
@ -6,7 +6,7 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
|
|
||||||
Vector2 startPosition = osuStart.EndPosition;
|
Vector2 startPosition = osuStart.EndPosition;
|
||||||
Vector2 endPosition = osuEnd.Position;
|
Vector2 endPosition = osuEnd.Position;
|
||||||
double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime;
|
double startTime = osuStart.GetEndTime();
|
||||||
double endTime = osuEnd.StartTime;
|
double endTime = osuEnd.StartTime;
|
||||||
|
|
||||||
Vector2 distanceVector = endPosition - startPosition;
|
Vector2 distanceVector = endPosition - startPosition;
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
|
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
||||||
|
|
||||||
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
float aimRotation = MathHelper.RadiansToDegrees((float)Math.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
|
float aimRotation = MathHelper.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
|
||||||
while (Math.Abs(aimRotation - Rotation) > 180)
|
while (Math.Abs(aimRotation - Rotation) > 180)
|
||||||
aimRotation += aimRotation < Rotation ? 360 : -360;
|
aimRotation += aimRotation < Rotation ? 360 : -360;
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly).
|
// If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly).
|
||||||
Rotation = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint);
|
Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuRulesetConfigManager config { get; set; }
|
private OsuRulesetConfigManager config { get; set; }
|
||||||
@ -83,9 +84,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||||
scaleBindable.BindTo(HitObject.ScaleBindable);
|
scaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
pathBindable.BindValueChanged(_ => Body.Refresh());
|
pathVersion.BindValueChanged(_ => Body.Refresh());
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Tracking.Value = Ball.Tracking;
|
Tracking.Value = Ball.Tracking;
|
||||||
|
|
||||||
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||||
|
|
||||||
Ball.UpdateProgress(completionProgress);
|
Ball.UpdateProgress(completionProgress);
|
||||||
Body.UpdateProgress(completionProgress);
|
Body.UpdateProgress(completionProgress);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
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 osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public class DrawableSliderHead : DrawableHitCircle
|
public class DrawableSliderHead : DrawableHitCircle
|
||||||
{
|
{
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
@ -27,17 +26,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
positionBindable.BindValueChanged(_ => updatePosition());
|
positionBindable.BindValueChanged(_ => updatePosition());
|
||||||
pathBindable.BindValueChanged(_ => updatePosition(), true);
|
pathVersion.BindValueChanged(_ => updatePosition(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||||
|
|
||||||
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
||||||
if (!IsHit)
|
if (!IsHit)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public bool Tracking { get; set; }
|
public bool Tracking { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
|
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
|
||||||
: base(hitCircle)
|
: base(hitCircle)
|
||||||
@ -36,10 +35,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
AlwaysPresent = true;
|
AlwaysPresent = true;
|
||||||
|
|
||||||
positionBindable.BindTo(hitCircle.PositionBindable);
|
positionBindable.BindTo(hitCircle.PositionBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
positionBindable.BindValueChanged(_ => updatePosition());
|
positionBindable.BindValueChanged(_ => updatePosition());
|
||||||
pathBindable.BindValueChanged(_ => updatePosition(), true);
|
pathVersion.BindValueChanged(_ => updatePosition(), true);
|
||||||
|
|
||||||
// TODO: This has no drawable content. Support for skins should be added.
|
// TODO: This has no drawable content. Support for skins should be added.
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
|
public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
CornerRadius = Size.X / 2;
|
CornerRadius = Size.X / 2;
|
||||||
|
CornerExponent = 2;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -18,10 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChild = new Container
|
InternalChild = new CircularContainer
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = Size.X / 2,
|
|
||||||
BorderThickness = 10,
|
BorderThickness = 10,
|
||||||
BorderColour = Color4.White,
|
BorderColour = Color4.White,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
var spanProgress = slider.ProgressAt(completionProgress);
|
var spanProgress = slider.ProgressAt(completionProgress);
|
||||||
|
|
||||||
double start = 0;
|
double start = 0;
|
||||||
double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
|
double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
|
||||||
|
|
||||||
if (span >= slider.SpanCount() - 1)
|
if (span >= slider.SpanCount() - 1)
|
||||||
{
|
{
|
||||||
|
@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
const int count = 18;
|
const float count = 18;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (float i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
Add(new Container
|
Add(new Container
|
||||||
{
|
{
|
||||||
@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Size = new Vector2(60, 10),
|
Size = new Vector2(60, 10),
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = new Vector2(
|
Position = new Vector2(
|
||||||
0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f,
|
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
|
||||||
0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f
|
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
|
||||||
),
|
),
|
||||||
Rotation = -(float)i / count * 360 + 90,
|
Rotation = -i / count * 360 + 90,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Types;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -28,17 +27,21 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
||||||
|
|
||||||
public readonly Bindable<SliderPath> PathBindable = new Bindable<SliderPath>();
|
private readonly SliderPath path = new SliderPath();
|
||||||
|
|
||||||
public SliderPath Path
|
public SliderPath Path
|
||||||
{
|
{
|
||||||
get => PathBindable.Value;
|
get => path;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
PathBindable.Value = value;
|
path.ControlPoints.Clear();
|
||||||
endPositionCache.Invalidate();
|
path.ExpectedDistance.Value = null;
|
||||||
|
|
||||||
updateNestedPositions();
|
if (value != null)
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(value.ControlPoints);
|
||||||
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +53,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.Position = value;
|
base.Position = value;
|
||||||
endPositionCache.Invalidate();
|
|
||||||
|
|
||||||
updateNestedPositions();
|
updateNestedPositions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
|
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
|
||||||
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
|
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
|
||||||
|
Path.Version.ValueChanged += _ => updateNestedPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
@ -189,6 +191,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
private void updateNestedPositions()
|
private void updateNestedPositions()
|
||||||
{
|
{
|
||||||
|
endPositionCache.Invalidate();
|
||||||
|
|
||||||
if (HeadCircle != null)
|
if (HeadCircle != null)
|
||||||
HeadCircle.Position = Position;
|
HeadCircle.Position = Position;
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SliderTailCircle : SliderCircle
|
public class SliderTailCircle : SliderCircle
|
||||||
{
|
{
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public SliderTailCircle(Slider slider)
|
public SliderTailCircle(Slider slider)
|
||||||
{
|
{
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
pathBindable.BindValueChanged(_ => Position = slider.EndPosition);
|
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
|
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
|
||||||
|
@ -10,7 +10,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
|
private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
|
||||||
{
|
{
|
||||||
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
|
double endTime = prev.GetEndTime();
|
||||||
|
|
||||||
HitWindows hitWindows = null;
|
HitWindows hitWindows = null;
|
||||||
|
|
||||||
@ -185,14 +185,14 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
{
|
{
|
||||||
Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos;
|
Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos;
|
||||||
float distFromCentre = spinCentreOffset.Length;
|
float distFromCentre = spinCentreOffset.Length;
|
||||||
float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
|
float distToTangentPoint = MathF.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
|
||||||
|
|
||||||
if (distFromCentre > SPIN_RADIUS)
|
if (distFromCentre > SPIN_RADIUS)
|
||||||
{
|
{
|
||||||
// Previous cursor position was outside spin circle, set startPosition to the tangent point.
|
// Previous cursor position was outside spin circle, set startPosition to the tangent point.
|
||||||
|
|
||||||
// Angle between centre offset and tangent point offset.
|
// Angle between centre offset and tangent point offset.
|
||||||
float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre);
|
float angle = MathF.Asin(SPIN_RADIUS / distFromCentre);
|
||||||
|
|
||||||
if (angle > 0)
|
if (angle > 0)
|
||||||
{
|
{
|
||||||
@ -204,8 +204,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rotate by angle so it's parallel to tangent line
|
// Rotate by angle so it's parallel to tangent line
|
||||||
spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle);
|
spinCentreOffset.X = spinCentreOffset.X * MathF.Cos(angle) - spinCentreOffset.Y * MathF.Sin(angle);
|
||||||
spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle);
|
spinCentreOffset.Y = spinCentreOffset.X * MathF.Sin(angle) + spinCentreOffset.Y * MathF.Cos(angle);
|
||||||
|
|
||||||
// Set length to distToTangentPoint
|
// Set length to distToTangentPoint
|
||||||
spinCentreOffset.Normalize();
|
spinCentreOffset.Normalize();
|
||||||
@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);
|
var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);
|
||||||
|
|
||||||
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
|
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
|
||||||
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
|
double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
|
||||||
int endDelay = h is Spinner ? 1 : 0;
|
int endDelay = h is Spinner ? 1 : 0;
|
||||||
var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
|
var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
|
||||||
|
|
||||||
@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
Vector2 difference = startPosition - SPINNER_CENTRE;
|
Vector2 difference = startPosition - SPINNER_CENTRE;
|
||||||
|
|
||||||
float radius = difference.Length;
|
float radius = difference.Length;
|
||||||
float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
|
float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X);
|
||||||
|
|
||||||
double t;
|
double t;
|
||||||
|
|
||||||
|
@ -5,22 +5,20 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
{
|
{
|
||||||
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
|
internal class OsuScoreProcessor : ScoreProcessor
|
||||||
{
|
{
|
||||||
public OsuScoreProcessor(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public OsuScoreProcessor(IBeatmap beatmap)
|
||||||
: base(drawableRuleset)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private float hpDrainRate;
|
private float hpDrainRate;
|
||||||
|
|
||||||
protected override void ApplyBeatmap(Beatmap<OsuHitObject> beatmap)
|
protected override void ApplyBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
base.ApplyBeatmap(beatmap);
|
base.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
@ -3,14 +3,16 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyCursor : CompositeDrawable
|
public class LegacyCursor : OsuCursorSprite
|
||||||
{
|
{
|
||||||
|
private bool spin;
|
||||||
|
|
||||||
public LegacyCursor()
|
public LegacyCursor()
|
||||||
{
|
{
|
||||||
Size = new Vector2(50);
|
Size = new Vector2(50);
|
||||||
@ -22,7 +24,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
new NonPlayfieldSprite
|
new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
@ -30,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
new NonPlayfieldSprite
|
ExpandTarget = new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursor"),
|
Texture = skin.GetTexture("cursor"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -38,5 +42,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
if (spin)
|
||||||
|
ExpandTarget.Spin(10000, RotationDirection.Clockwise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
return this.GetAnimation(component.LookupName, true, false);
|
return this.GetAnimation(component.LookupName, true, false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
return this.GetAnimation("sliderfollowcircle", true, true);
|
var followCircle = this.GetAnimation("sliderfollowcircle", true, true);
|
||||||
|
if (followCircle != null)
|
||||||
|
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||||
|
followCircle.Scale *= 0.5f;
|
||||||
|
return followCircle;
|
||||||
|
|
||||||
case OsuSkinComponents.SliderBall:
|
case OsuSkinComponents.SliderBall:
|
||||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
|
var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
|
||||||
|
@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
AllowSliderBallTint,
|
AllowSliderBallTint,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
|
CursorRotate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
private bool cursorExpand;
|
private bool cursorExpand;
|
||||||
|
|
||||||
private Container expandTarget;
|
private SkinnableDrawable cursorSprite;
|
||||||
|
|
||||||
|
private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite;
|
||||||
|
|
||||||
public OsuCursor()
|
public OsuCursor()
|
||||||
{
|
{
|
||||||
@ -37,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = expandTarget = new Container
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
|
Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
|
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
|
||||||
|
|
||||||
private class DefaultCursor : CompositeDrawable
|
private class DefaultCursor : OsuCursorSprite
|
||||||
{
|
{
|
||||||
public DefaultCursor()
|
public DefaultCursor()
|
||||||
{
|
{
|
||||||
@ -71,10 +73,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
ExpandTarget = new CircularContainer
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
BorderThickness = size / 6,
|
BorderThickness = size / 6,
|
||||||
|
17
osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs
Normal file
17
osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||||
|
{
|
||||||
|
public abstract class OsuCursorSprite : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The an optional piece of the cursor to expand when in a clicked state.
|
||||||
|
/// If null, the whole cursor will be affected by expansion.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable ExpandTarget { get; protected set; }
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Scoring;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
@ -30,7 +31,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
|
||||||
|
|
||||||
|
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap);
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new OsuPlayfield();
|
protected override Playfield CreatePlayfield() => new OsuPlayfield();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>click the circles. to the beat.</Description>
|
<Description>click the circles. to the beat.</Description>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Taiko.Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Taiko.Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!taiko Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!taiko Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
yield return new ConvertValue
|
yield return new ConvertValue
|
||||||
{
|
{
|
||||||
StartTime = hitObject.StartTime,
|
StartTime = hitObject.StartTime,
|
||||||
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
|
EndTime = hitObject.GetEndTime(),
|
||||||
IsRim = hitObject is RimHit,
|
IsRim = hitObject is RimHit,
|
||||||
IsCentre = hitObject is CentreHit,
|
IsCentre = hitObject is CentreHit,
|
||||||
IsDrumRoll = hitObject is DrumRoll,
|
IsDrumRoll = hitObject is DrumRoll,
|
||||||
|
@ -73,127 +73,133 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
protected override IEnumerable<TaikoHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
|
protected override IEnumerable<TaikoHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var distanceData = obj as IHasDistance;
|
|
||||||
var repeatsData = obj as IHasRepeats;
|
|
||||||
var endTimeData = obj as IHasEndTime;
|
|
||||||
var curveData = obj as IHasCurve;
|
|
||||||
|
|
||||||
// Old osu! used hit sounding to determine various hit type information
|
// Old osu! used hit sounding to determine various hit type information
|
||||||
IList<HitSampleInfo> samples = obj.Samples;
|
IList<HitSampleInfo> samples = obj.Samples;
|
||||||
|
|
||||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||||
|
|
||||||
if (distanceData != null)
|
switch (obj)
|
||||||
{
|
{
|
||||||
// Number of spans of the object - one for the initial length and for each repeat
|
case IHasDistance distanceData:
|
||||||
int spans = repeatsData?.SpanCount() ?? 1;
|
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
|
||||||
|
|
||||||
double speedAdjustment = difficultyPoint.SpeedMultiplier;
|
|
||||||
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
|
|
||||||
|
|
||||||
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
|
||||||
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
|
|
||||||
|
|
||||||
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
|
|
||||||
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
|
||||||
// The duration of the taiko hit object
|
|
||||||
double taikoDuration = distance / taikoVelocity;
|
|
||||||
|
|
||||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
|
||||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
|
||||||
// The duration of the osu! hit object
|
|
||||||
double osuDuration = distance / osuVelocity;
|
|
||||||
|
|
||||||
// osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
|
|
||||||
// only uses it for tick rate if beatmap version < 8
|
|
||||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
|
|
||||||
speedAdjustedBeatLength *= speedAdjustment;
|
|
||||||
|
|
||||||
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
|
||||||
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
|
|
||||||
|
|
||||||
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
|
|
||||||
{
|
{
|
||||||
List<IList<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
// Number of spans of the object - one for the initial length and for each repeat
|
||||||
|
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
|
||||||
|
|
||||||
int i = 0;
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||||
|
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
||||||
|
|
||||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
double speedAdjustment = difficultyPoint.SpeedMultiplier;
|
||||||
|
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
|
||||||
|
|
||||||
|
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
||||||
|
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
|
||||||
|
|
||||||
|
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
|
||||||
|
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
||||||
|
// The duration of the taiko hit object
|
||||||
|
double taikoDuration = distance / taikoVelocity;
|
||||||
|
|
||||||
|
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||||
|
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
||||||
|
// The duration of the osu! hit object
|
||||||
|
double osuDuration = distance / osuVelocity;
|
||||||
|
|
||||||
|
// osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
|
||||||
|
// only uses it for tick rate if beatmap version < 8
|
||||||
|
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
|
||||||
|
speedAdjustedBeatLength *= speedAdjustment;
|
||||||
|
|
||||||
|
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
||||||
|
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
|
||||||
|
|
||||||
|
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
|
||||||
{
|
{
|
||||||
IList<HitSampleInfo> currentSamples = allSamples[i];
|
List<IList<HitSampleInfo>> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
|
||||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
|
||||||
|
|
||||||
if (isRim)
|
int i = 0;
|
||||||
{
|
|
||||||
yield return new RimHit
|
|
||||||
{
|
|
||||||
StartTime = j,
|
|
||||||
Samples = currentSamples,
|
|
||||||
IsStrong = strong
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return new CentreHit
|
|
||||||
{
|
|
||||||
StartTime = j,
|
|
||||||
Samples = currentSamples,
|
|
||||||
IsStrong = strong
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
i = (i + 1) % allSamples.Count;
|
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||||
|
{
|
||||||
|
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||||
|
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||||
|
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||||
|
|
||||||
|
if (isRim)
|
||||||
|
{
|
||||||
|
yield return new RimHit
|
||||||
|
{
|
||||||
|
StartTime = j,
|
||||||
|
Samples = currentSamples,
|
||||||
|
IsStrong = strong
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return new CentreHit
|
||||||
|
{
|
||||||
|
StartTime = j,
|
||||||
|
Samples = currentSamples,
|
||||||
|
IsStrong = strong
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i = (i + 1) % allSamples.Count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return new DrumRoll
|
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
yield return new DrumRoll
|
||||||
Samples = obj.Samples,
|
{
|
||||||
IsStrong = strong,
|
StartTime = obj.StartTime,
|
||||||
Duration = taikoDuration,
|
Samples = obj.Samples,
|
||||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
IsStrong = strong,
|
||||||
};
|
Duration = taikoDuration,
|
||||||
}
|
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||||
}
|
};
|
||||||
else if (endTimeData != null)
|
}
|
||||||
{
|
|
||||||
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
|
|
||||||
|
|
||||||
yield return new Swell
|
break;
|
||||||
{
|
|
||||||
StartTime = obj.StartTime,
|
|
||||||
Samples = obj.Samples,
|
|
||||||
Duration = endTimeData.Duration,
|
|
||||||
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
|
||||||
|
|
||||||
if (isRim)
|
|
||||||
{
|
|
||||||
yield return new RimHit
|
|
||||||
{
|
|
||||||
StartTime = obj.StartTime,
|
|
||||||
Samples = obj.Samples,
|
|
||||||
IsStrong = strong
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
case IHasEndTime endTimeData:
|
||||||
{
|
{
|
||||||
yield return new CentreHit
|
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
|
||||||
|
|
||||||
|
yield return new Swell
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
IsStrong = strong
|
Duration = endTimeData.Duration,
|
||||||
|
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
if (isRim)
|
||||||
|
{
|
||||||
|
yield return new RimHit
|
||||||
|
{
|
||||||
|
StartTime = obj.StartTime,
|
||||||
|
Samples = obj.Samples,
|
||||||
|
IsStrong = strong
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return new CentreHit
|
||||||
|
{
|
||||||
|
StartTime = obj.StartTime,
|
||||||
|
Samples = obj.Samples,
|
||||||
|
IsStrong = strong
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
|
countGreat = Score.Statistics[HitResult.Great];
|
||||||
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
|
countGood = Score.Statistics[HitResult.Good];
|
||||||
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
|
countMeh = Score.Statistics[HitResult.Meh];
|
||||||
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
|
countMiss = Score.Statistics[HitResult.Miss];
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
// Don't count scores made with supposedly unranked mods
|
||||||
if (mods.Any(m => !m.Ranked))
|
if (mods.Any(m => !m.Ranked))
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
|
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
|
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
||||||
strainValue *= lengthBonus;
|
strainValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
else
|
else
|
||||||
rollingHits--;
|
rollingHits--;
|
||||||
|
|
||||||
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
|
rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
|
||||||
|
|
||||||
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||||
MainPiece.FadeAccent(newColour, 100);
|
MainPiece.FadeAccent(newColour, 100);
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
var completion = (float)numHits / HitObject.RequiredHits;
|
var completion = (float)numHits / HitObject.RequiredHits;
|
||||||
|
|
||||||
expandingRing
|
expandingRing
|
||||||
.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
|
.FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
||||||
|
|
||||||
|
@ -10,27 +10,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
|
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
|
||||||
{
|
{
|
||||||
private Color4 accentColour;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour of the inner circle and outer glows.
|
/// The colour of the inner circle and outer glows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Color4 AccentColour
|
public virtual Color4 AccentColour { get; set; }
|
||||||
{
|
|
||||||
get => accentColour;
|
|
||||||
set => accentColour = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool kiaiMode;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether Kiai mode effects are enabled for this circle piece.
|
/// Whether Kiai mode effects are enabled for this circle piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool KiaiMode
|
public virtual bool KiaiMode { get; set; }
|
||||||
{
|
|
||||||
get => kiaiMode;
|
|
||||||
set => kiaiMode = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaikoPiece()
|
public TaikoPiece()
|
||||||
{
|
{
|
||||||
|
@ -43,76 +43,83 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
IHasEndTime endTimeData = h as IHasEndTime;
|
IHasEndTime endTimeData = h as IHasEndTime;
|
||||||
double endTime = endTimeData?.EndTime ?? h.StartTime;
|
double endTime = endTimeData?.EndTime ?? h.StartTime;
|
||||||
|
|
||||||
Swell swell = h as Swell;
|
switch (h)
|
||||||
DrumRoll drumRoll = h as DrumRoll;
|
|
||||||
Hit hit = h as Hit;
|
|
||||||
|
|
||||||
if (swell != null)
|
|
||||||
{
|
{
|
||||||
int d = 0;
|
case Swell swell:
|
||||||
int count = 0;
|
|
||||||
int req = swell.RequiredHits;
|
|
||||||
double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
|
|
||||||
|
|
||||||
for (double j = h.StartTime; j < endTime; j += hitRate)
|
|
||||||
{
|
{
|
||||||
TaikoAction action;
|
int d = 0;
|
||||||
|
int count = 0;
|
||||||
|
int req = swell.RequiredHits;
|
||||||
|
double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
|
||||||
|
|
||||||
switch (d)
|
for (double j = h.StartTime; j < endTime; j += hitRate)
|
||||||
{
|
{
|
||||||
default:
|
TaikoAction action;
|
||||||
case 0:
|
|
||||||
action = TaikoAction.LeftCentre;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
switch (d)
|
||||||
action = TaikoAction.LeftRim;
|
{
|
||||||
break;
|
default:
|
||||||
|
case 0:
|
||||||
|
action = TaikoAction.LeftCentre;
|
||||||
|
break;
|
||||||
|
|
||||||
case 2:
|
case 1:
|
||||||
action = TaikoAction.RightCentre;
|
action = TaikoAction.LeftRim;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 2:
|
||||||
action = TaikoAction.RightRim;
|
action = TaikoAction.RightCentre;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
action = TaikoAction.RightRim;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frames.Add(new TaikoReplayFrame(j, action));
|
||||||
|
d = (d + 1) % 4;
|
||||||
|
if (++count == req)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Frames.Add(new TaikoReplayFrame(j, action));
|
break;
|
||||||
d = (d + 1) % 4;
|
|
||||||
if (++count == req)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (drumRoll != null)
|
|
||||||
{
|
|
||||||
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
|
|
||||||
{
|
|
||||||
Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
|
|
||||||
hitButton = !hitButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (hit != null)
|
|
||||||
{
|
|
||||||
TaikoAction[] actions;
|
|
||||||
|
|
||||||
if (hit is CentreHit)
|
|
||||||
{
|
|
||||||
actions = h.IsStrong
|
|
||||||
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
|
||||||
: new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actions = h.IsStrong
|
|
||||||
? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
|
|
||||||
: new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
|
case DrumRoll drumRoll:
|
||||||
|
{
|
||||||
|
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
|
||||||
|
{
|
||||||
|
Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
|
||||||
|
hitButton = !hitButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Hit hit:
|
||||||
|
{
|
||||||
|
TaikoAction[] actions;
|
||||||
|
|
||||||
|
if (hit is CentreHit)
|
||||||
|
{
|
||||||
|
actions = h.IsStrong
|
||||||
|
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
||||||
|
: new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions = h.IsStrong
|
||||||
|
? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
|
||||||
|
: new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
|
||||||
|
}
|
||||||
|
|
||||||
|
Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("Unknown hit object type.");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw new InvalidOperationException("Unknown hit object type.");
|
|
||||||
|
|
||||||
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
|
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user