1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 04:02:57 +08:00

Merge branch 'master' into rankings-tables

This commit is contained in:
Dean Herbert 2019-11-27 23:26:54 +09:00 committed by GitHub
commit 4221a0126c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
286 changed files with 2784 additions and 1272 deletions

View File

@ -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

View File

@ -0,0 +1,5 @@
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.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.

View File

@ -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.7" 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
View 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

View File

@ -4,7 +4,10 @@
# 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.
@ -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!
@ -70,13 +74,19 @@ If you are not interested in debugging osu!, you can add `-c Release` to gain pe
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 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. It can be achieved with `dotnet format` in command line, or `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

View File

@ -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\**\*'

View File

@ -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

View File

@ -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[@]}"

View File

@ -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);

View File

@ -1,5 +1,5 @@
{ {
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.19" "Microsoft.Build.Traversal": "2.0.24"
} }
} }

View File

@ -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.1126.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -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" />

View File

@ -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;

View File

@ -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>

View File

@ -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"
} }

View File

@ -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,15 +23,13 @@ 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, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
@ -40,30 +39,28 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0, ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0 LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
}; }.Yield();
}
else if (endTime != null) case IHasEndTime endTime:
{ return new BananaShower
yield 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();
} }
} }

View File

@ -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;

View File

@ -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
{ {
@ -48,55 +47,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;

View File

@ -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)

View File

@ -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))
{ {

View File

@ -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)

View File

@ -116,13 +116,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
private SliderPath path; public SliderPath Path { get; set; }
public SliderPath Path
{
get => path;
set => path = value;
}
public double Distance => Path.Distance; public double Distance => Path.Distance;

View File

@ -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);
} }
@ -378,7 +378,7 @@ namespace osu.Game.Rulesets.Catch.UI
double speed = BASE_SPEED * dashModifier * hyperDashModifier; double speed = BASE_SPEED * dashModifier * hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
// Correct overshooting. // Correct overshooting.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||

View File

@ -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>

View File

@ -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>

View File

@ -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
}; };
} }

View File

@ -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)
{
case IHasDistance _:
{ {
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
conversion = generator; conversion = generator;
var positionData = original as IHasPosition;
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration) for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
{ {
recordNote(time, positionData?.Position ?? Vector2.Zero); recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time); computeDensity(time);
} }
break;
} }
else if (endTimeData != null)
case IHasEndTime endTimeData:
{ {
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
recordNote(endTimeData.EndTime, new Vector2(256, 192)); recordNote(endTimeData.EndTime, new Vector2(256, 192));
computeDensity(endTimeData.EndTime); computeDensity(endTimeData.EndTime);
break;
} }
else if (positionData != null)
case IHasPosition positionData:
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position); 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();

View File

@ -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;

View File

@ -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(); var pattern = new Pattern();
try
{
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 pattern.HitObjects)
foreach (var obj in p.HitObjects)
{ {
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair; StairType = PatternType.ReverseStair;
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair; 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:

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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>

View File

@ -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>

View File

@ -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
}; };

View File

@ -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
{ {

View File

@ -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),

View File

@ -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;

View File

@ -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;
} }

View File

@ -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,15 +24,13 @@ 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, StartTime = original.StartTime,
Samples = original.Samples, Samples = original.Samples,
@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
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,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset, LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
// this results in more (or less) ticks being generated in <v8 maps for the same time duration. // 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 TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
}; }.Yield();
}
else if (endTimeData != null) case IHasEndTime endTimeData:
{ return new Spinner
yield return new Spinner
{ {
StartTime = original.StartTime, StartTime = original.StartTime,
Samples = original.Samples, Samples = original.Samples,
@ -57,18 +55,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
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();
} }
} }

View File

@ -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();
} }
} }
} }

View File

@ -55,22 +55,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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;
} }

View File

@ -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;

View File

@ -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();
} }

View File

@ -14,12 +14,13 @@ using osu.Game.Rulesets.Edit;
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<int, MouseButtonEvent> RequestSelection;
public Action<Vector2[]> ControlPointsChanged; public Action<Vector2[]> ControlPointsChanged;
public readonly BindableBool IsSelected = new BindableBool(); public readonly BindableBool IsSelected = new BindableBool();
@ -129,10 +130,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
if (RequestSelection != null) if (RequestSelection == null)
return false;
switch (e.Button)
{ {
RequestSelection.Invoke(Index); case MouseButton.Left:
RequestSelection.Invoke(Index, e);
return true; return true;
case MouseButton.Right:
if (!IsSelected.Value)
RequestSelection.Invoke(Index, e);
return false; // Allow context menu to show
} }
return false; return false;
@ -142,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnClick(ClickEvent e) => RequestSelection != null; protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left;
protected override bool OnDrag(DragEvent e) protected override bool OnDrag(DragEvent e)
{ {

View File

@ -3,19 +3,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.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.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osuTK; using osuTK;
using osuTK.Input;
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; public Action<Vector2[]> ControlPointsChanged;
@ -73,9 +79,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false; return false;
} }
private void selectPiece(int index) public bool OnPressed(PlatformAction action)
{ {
if (inputManager.CurrentState.Keyboard.ControlPressed) switch (action.ActionMethod)
{
case PlatformActionMethod.Delete:
return deleteSelected();
}
return false;
}
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
private void selectPiece(int index, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
Pieces[index].IsSelected.Toggle(); Pieces[index].IsSelected.Toggle();
else else
{ {
@ -84,11 +103,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
} }
} }
public bool OnPressed(PlatformAction action) private bool deleteSelected()
{ {
switch (action.ActionMethod)
{
case PlatformActionMethod.Delete:
var newControlPoints = new List<Vector2>(); var newControlPoints = new List<Vector2>();
foreach (var piece in Pieces) foreach (var piece in Pieces)
@ -114,20 +130,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
newControlPoints[i] = newControlPoints[i] - first; 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 // 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; slider.Position += first;
// Since pieces are re-used, they will not point to the deleted control points while remaining selected // Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces) foreach (var piece in Pieces)
piece.IsSelected.Value = false; piece.IsSelected.Value = false;
ControlPointsChanged?.Invoke(newControlPoints.ToArray()); ControlPointsChanged?.Invoke(newControlPoints.ToArray());
return true; return true;
} }
return false; public MenuItem[] ContextMenuItems
} {
get
{
if (!Pieces.Any(p => p.IsHovered))
return null;
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
if (selectedPoints == 0)
return null;
return new MenuItem[]
{
new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected())
};
}
}
} }
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -22,8 +22,7 @@ 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));

View File

@ -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)
{ {

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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);
} }
} }
} }

View File

@ -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;
@ -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);

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
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)

View File

@ -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)
{ {

View File

@ -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;

View File

@ -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,

View File

@ -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)
{ {

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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,

View File

@ -73,20 +73,17 @@ 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)
{
case IHasDistance distanceData:
{ {
// Number of spans of the object - one for the initial length and for each repeat // Number of spans of the object - one for the initial length and for each repeat
int spans = repeatsData?.SpanCount() ?? 1; int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
@ -117,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{ {
List<IList<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples }); List<IList<HitSampleInfo>> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
int i = 0; int i = 0;
@ -160,8 +157,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
}; };
} }
break;
} }
else if (endTimeData != null)
case IHasEndTime endTimeData:
{ {
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
@ -172,8 +172,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
}; };
break;
} }
else
default:
{ {
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
@ -195,6 +198,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
IsStrong = strong IsStrong = strong
}; };
} }
break;
}
} }
} }

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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()
{ {

View File

@ -43,11 +43,9 @@ 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; case Swell swell:
if (swell != null)
{ {
int d = 0; int d = 0;
int count = 0; int count = 0;
@ -83,16 +81,22 @@ namespace osu.Game.Rulesets.Taiko.Replays
if (++count == req) if (++count == req)
break; break;
} }
break;
} }
else if (drumRoll != null)
case DrumRoll drumRoll:
{ {
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre)); Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
hitButton = !hitButton; hitButton = !hitButton;
} }
break;
} }
else if (hit != null)
case Hit hit:
{ {
TaikoAction[] actions; TaikoAction[] actions;
@ -110,9 +114,12 @@ namespace osu.Game.Rulesets.Taiko.Replays
} }
Frames.Add(new TaikoReplayFrame(h.StartTime, actions)); Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
break;
} }
else
default:
throw new InvalidOperationException("Unknown hit object type."); 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

View File

@ -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.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
base.Update(); base.Update();
float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
Size = new Vector2(1, default_relative_height * aspectAdjust); Size = new Vector2(1, default_relative_height * aspectAdjust);
} }
} }

View File

@ -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>bash the drum. to the beat.</Description> <Description>bash the drum. to the beat.</Description>

View File

@ -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.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.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!visual Test" /> <application android:allowBackup="true" android:supportsRtl="true" android:label="osu!visual Test" />
</manifest> </manifest>

View File

@ -24,6 +24,7 @@
<Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs"> <Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link> <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile> </Compile>
<Compile Remove="..\osu.Game.Tests\Beatmaps\Formats\OsuJsonDecoderTest.cs" />
<Compile Include="..\osu.Game.Tests\**\Chat\**\*.cs"> <Compile Include="..\osu.Game.Tests\**\Chat\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link> <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile> </Compile>
@ -68,10 +69,5 @@
<Name>osu.Game</Name> <Name>osu.Game</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="DeepEqual">
<Version>2.0.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -413,7 +413,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
} }
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]
@ -431,7 +431,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
} }
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]
@ -451,7 +451,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
} }
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]

View File

@ -59,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{ {
try try
{ {
var _ = int.Parse(input); _ = int.Parse(input);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -411,6 +411,48 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
[Test]
public async Task TestImportWithDuplicateHashes()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
[Test] [Test]
public async Task TestImportNestedStructure() public async Task TestImportNestedStructure()
{ {

View File

@ -87,7 +87,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsNull(filterCriteria.BPM.Max); Assert.IsNull(filterCriteria.BPM.Max);
} }
private static object[] lengthQueryExamples = private static readonly object[] length_query_examples =
{ {
new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
@ -97,7 +97,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
}; };
[Test] [Test]
[TestCaseSource(nameof(lengthQueryExamples))] [TestCaseSource(nameof(length_query_examples))]
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
{ {
string query = $"length={lengthQuery} time"; string query = $"length={lengthQuery} time";

View File

@ -0,0 +1,2 @@
[General]
Version: 2

View File

@ -0,0 +1,2 @@
[General]
Version: latest

View File

@ -59,5 +59,32 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]); Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
} }
} }
[Test]
public void TestDecodeSpecifiedVersion()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-20.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
[Test]
public void TestDecodeLatestVersion()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-latest.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.AreEqual(LegacySkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion);
}
[Test]
public void TestDecodeNoVersion()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.IsNull(decoder.Decode(stream).LegacyVersion);
}
} }
} }

View File

@ -116,6 +116,14 @@ namespace osu.Game.Tests.Skins
}); });
} }
[Test]
public void TestLegacyVersionLookup()
{
AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m);
AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
}
public enum LookupType public enum LookupType
{ {
Test Test

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Audio; using osu.Game.Audio;
@ -13,7 +14,9 @@ namespace osu.Game.Tests.Visual.Components
{ {
public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner
{ {
private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager(); private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager();
private AudioManager audio;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
@ -24,8 +27,10 @@ namespace osu.Game.Tests.Visual.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(AudioManager audio)
{ {
this.audio = audio;
Add(trackManager); Add(trackManager);
} }
@ -108,6 +113,60 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning); AddAssert("track stopped", () => !track.IsRunning);
} }
/// <summary>
/// Ensures that <see cref="PreviewTrackManager.CurrentTrack"/> changes correctly.
/// </summary>
[Test]
public void TestCurrentTrackChanges()
{
PreviewTrack track = null;
TestTrackOwner owner = null;
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start track", () => track.Start());
AddAssert("current is track", () => trackManager.CurrentTrack == track);
AddStep("pause manager updates", () => trackManager.AllowUpdate = false);
AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
AddAssert("current not changed", () => trackManager.CurrentTrack == track);
AddStep("resume manager updates", () => trackManager.AllowUpdate = true);
AddAssert("current is null", () => trackManager.CurrentTrack == null);
}
/// <summary>
/// Ensures that <see cref="PreviewTrackManager"/> mutes game-wide audio tracks correctly.
/// </summary>
[TestCase(false)]
[TestCase(true)]
public void TestEnsureMutingCorrectly(bool stopAnyPlaying)
{
PreviewTrack track = null;
TestTrackOwner owner = null;
AddStep("ensure volume not zero", () =>
{
if (audio.Volume.Value == 0)
audio.Volume.Value = 1;
if (audio.VolumeTrack.Value == 0)
audio.VolumeTrack.Value = 1;
});
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start track", () => track.Start());
AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0);
if (stopAnyPlaying)
AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
else
AddStep("stop track", () => track.Stop());
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
}
private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrack getOwnedTrack() private TestPreviewTrack getOwnedTrack()
@ -144,8 +203,20 @@ namespace osu.Game.Tests.Visual.Components
public class TestPreviewTrackManager : PreviewTrackManager public class TestPreviewTrackManager : PreviewTrackManager
{ {
public bool AllowUpdate = true;
public new PreviewTrack CurrentTrack => base.CurrentTrack;
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
public override bool UpdateSubTree()
{
if (!AllowUpdate)
return true;
return base.UpdateSubTree();
}
public class TestPreviewTrack : TrackManagerPreviewTrack public class TestPreviewTrack : TrackManagerPreviewTrack
{ {
private readonly ITrackStore trackManager; private readonly ITrackStore trackManager;

View File

@ -7,6 +7,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -29,9 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
} }
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{ {
var working = base.CreateWorkingBeatmap(beatmap); var working = base.CreateWorkingBeatmap(beatmap, storyboard);
track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track; track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Scoring;
@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = new[] Children = new[]
{ {
new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
} }
}); });

View File

@ -95,6 +95,19 @@ namespace osu.Game.Tests.Visual.Gameplay
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false); seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
} }
[TestCase(true)]
[TestCase(false)]
public void TestBeforeGameplayStart(bool withBreaks)
{
setClock(true);
if (withBreaks)
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break intro time", -100, true);
seekAndAssertBreak("seek to break intro time", 0, false);
}
private void addShowBreakStep(double seconds) private void addShowBreakStep(double seconds)
{ {
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod> AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod>

View File

@ -17,6 +17,7 @@ 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.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -35,9 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private Track track; private Track track;
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 = working.Track; track = working.Track;
return working; return working;
} }

View File

@ -0,0 +1,113 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneLeadIn : RateAdjustedBeatmapTestScene
{
private LeadInPlayer player;
private const double lenience_ms = 10;
private const double first_hit_object = 2170;
[TestCase(1000, 0)]
[TestCase(2000, 0)]
[TestCase(3000, first_hit_object - 3000)]
[TestCase(10000, first_hit_object - 10000)]
public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime)
{
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = { AudioLeadIn = leadIn }
});
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
[TestCase(1000, 0)]
[TestCase(0, 0)]
[TestCase(-1000, -1000)]
[TestCase(-10000, -10000)]
public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
AddStep("create player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard);
LoadScreen(player = new LeadInPlayer());
});
AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1);
}
private class LeadInPlayer : TestPlayer
{
public LeadInPlayer()
: base(false, false)
{
}
public double? FirstFrameClockTime;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public double GameplayStartTime => DrawableRuleset.GameplayStartTime;
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (!FirstFrameClockTime.HasValue)
{
FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
+ $"FirstHitObjectTime: {FirstHitObjectTime} "
+ $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} "
+ $"FirstFrameClockTime: {FirstFrameClockTime}"
});
}
}
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Lists;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
} }
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{ {
var working = base.CreateWorkingBeatmap(beatmap); var working = base.CreateWorkingBeatmap(beatmap, storyboard);
workingWeakReferences.Add(working); workingWeakReferences.Add(working);
return working; return working;
} }

View File

@ -335,16 +335,14 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestSkinComponent : ISkinComponent private class TestSkinComponent : ISkinComponent
{ {
private readonly string name;
public TestSkinComponent(string name) public TestSkinComponent(string name)
{ {
this.name = name; LookupName = name;
} }
public string ComponentGroup => string.Empty; public string ComponentGroup => string.Empty;
public string LookupName => name; public string LookupName { get; }
} }
} }
} }

View File

@ -4,8 +4,8 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods;
using osu.Framework.Timing; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -18,25 +18,37 @@ namespace osu.Game.Tests.Visual.Gameplay
private SkipOverlay skip; private SkipOverlay skip;
private int requestCount; private int requestCount;
private double increment;
private GameplayClockContainer gameplayClockContainer;
private GameplayClock gameplayClock;
private const double skip_time = 6000;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
requestCount = 0; requestCount = 0;
Child = new Container increment = skip_time;
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock)
{
Offset = -Clock.CurrentTime,
},
Children = new Drawable[] Children = new Drawable[]
{ {
skip = new SkipOverlay(6000) skip = new SkipOverlay(skip_time)
{ {
RequestSeek = _ => requestCount++ RequestSkip = () =>
{
requestCount++;
gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment);
}
} }
}, },
}; };
gameplayClockContainer.Start();
gameplayClock = gameplayClockContainer.GameplayClock;
}); });
[Test] [Test]
@ -64,19 +76,35 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestClickOnlyActuatesOnce() public void TestClickOnlyActuatesOnce()
{ {
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () =>
{
increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2;
InputManager.Click(MouseButton.Left);
});
AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1); checkRequestCount(1);
} }
[Test]
public void TestClickOnlyActuatesMultipleTimes()
{
AddStep("set increment lower", () => increment = 3000);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(2);
}
[Test] [Test]
public void TestDoesntFadeOnMouseDown() public void TestDoesntFadeOnMouseDown()
{ {
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left)); AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disapper", () => !skip.IsAlive); AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0); AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0); checkRequestCount(0);

View File

@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability), typeof(BeatmapAvailability),
typeof(BeatmapRulesetSelector), typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem), typeof(BeatmapRulesetTabItem),
typeof(NotSupporterPlaceholder)
}; };
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;

View File

@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => successRate.Beatmap = secondBeatmap); AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics); AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
BeatmapInfo createBeatmap() => new BeatmapInfo static BeatmapInfo createBeatmap() => new BeatmapInfo
{ {
Metrics = new BeatmapMetrics Metrics = new BeatmapMetrics
{ {

View File

@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays.BeatmapSet;
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Catch;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Bindables;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneLeaderboardModSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LeaderboardModSelector),
};
public TestSceneLeaderboardModSelector()
{
LeaderboardModSelector modSelector;
FillFlowContainer<SpriteText> selectedMods;
var ruleset = new Bindable<RulesetInfo>();
Add(selectedMods = new FillFlowContainer<SpriteText>
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
});
Add(modSelector = new LeaderboardModSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Ruleset = { BindTarget = ruleset }
});
modSelector.SelectedMods.ItemsAdded += mods =>
{
mods.ForEach(mod => selectedMods.Add(new OsuSpriteText
{
Text = mod.Acronym,
}));
};
modSelector.SelectedMods.ItemsRemoved += mods =>
{
mods.ForEach(mod =>
{
foreach (var selected in selectedMods)
{
if (selected.Text == mod.Acronym)
{
selectedMods.Remove(selected);
break;
}
}
});
};
AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo);
AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo);
AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo);
AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo);
AddStep("Deselect all", () => modSelector.DeselectAll());
AddStep("null ruleset", () => ruleset.Value = null);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsOverlay : OsuTestScene
{
private NewsOverlay news;
protected override void LoadComplete()
{
base.LoadComplete();
Add(news = new NewsOverlay());
AddStep(@"Show", news.Show);
AddStep(@"Hide", news.Hide);
AddStep(@"Show front page", () => news.ShowFrontPage());
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Rankings; using osu.Game.Overlays.Rankings;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online
Size = new Vector2(30, 20), Size = new Vector2(30, 20),
Country = countryA, Country = countryA,
}, },
text = new SpriteText text = new OsuSpriteText
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,

View File

@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Overlays.Comments;
using osu.Framework.MathUtils;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneTotalCommentsCounter : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(TotalCommentsCounter),
};
public TestSceneTotalCommentsCounter()
{
var count = new BindableInt();
Add(new TotalCommentsCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = count }
});
AddStep(@"Set 100", () => count.Value = 100);
AddStep(@"Set 0", () => count.Value = 0);
AddStep(@"Set random", () => count.Value = RNG.Next(0, int.MaxValue));
}
}
}

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