diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
similarity index 100%
rename from ISSUE_TEMPLATE.md
rename to .github/ISSUE_TEMPLATE.md
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
deleted file mode 100644
index 221e4746cb..0000000000
--- a/.github/pull_request_template.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Add any details pertaining to developers above the break.
-
-- [ ] Depends on #PR
-- Closes #ISSUE
-
----
-
-Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f95a04e517..0e2850a01c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,8 +11,10 @@
*.userprefs
### Cake ###
-tools/*
-!tools/cakebuild.csproj
+tools/**
+build/tools/**
+
+fastlane/report.xml
# Build results
bin/[Dd]ebug/
diff --git a/.gitmodules b/.gitmodules
index f1c4f5d172..e69de29bb2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "osu-resources"]
- path = osu-resources
- url = https://github.com/ppy/osu-resources
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 10c6f02314..c3306c2db7 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -68,6 +68,20 @@
}
},
"console": "internalConsole"
+ },
+ {
+ "name": "Cake: Debug Script",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll",
+ "args": [
+ "${workspaceRoot}/build/build.cake",
+ "--debug",
+ "--verbosity=diagnostic"
+ ],
+ "cwd": "${workspaceRoot}/build",
+ "stopAtEntry": true,
+ "externalConsole": false
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2e9bec22c2..de799a7c03 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -70,7 +70,8 @@
"type": "shell",
"command": "dotnet",
"args": [
- "restore"
+ "restore",
+ "osu.sln"
],
"problemMatcher": []
}
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000..cdd3a6b349
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+gem "fastlane"
+
+plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
+eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..17c0db12e7
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,173 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.0)
+ addressable (2.6.0)
+ public_suffix (>= 2.0.2, < 4.0)
+ atomos (0.1.3)
+ babosa (1.0.2)
+ claide (1.0.2)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander-fastlane (4.4.6)
+ highline (~> 1.7.2)
+ declarative (0.0.10)
+ declarative-option (0.1.0)
+ digest-crc (0.4.1)
+ domain_name (0.5.20180417)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.1)
+ emoji_regex (1.0.1)
+ excon (0.62.0)
+ faraday (0.15.4)
+ multipart-post (>= 1.2, < 3)
+ faraday-cookie_jar (0.0.6)
+ faraday (>= 0.7.4)
+ http-cookie (~> 1.0.0)
+ faraday_middleware (0.13.1)
+ faraday (>= 0.7.4, < 1.0)
+ fastimage (2.1.5)
+ fastlane (2.117.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.3, < 3.0.0)
+ babosa (>= 1.0.2, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander-fastlane (>= 4.4.6, < 5.0.0)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 2.0)
+ excon (>= 0.45.0, < 1.0.0)
+ faraday (~> 0.9)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 0.9)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-api-client (>= 0.21.2, < 0.24.0)
+ google-cloud-storage (>= 1.15.0, < 2.0.0)
+ highline (>= 1.7.2, < 2.0.0)
+ json (< 3.0.0)
+ mini_magick (~> 4.5.1)
+ multi_json
+ multi_xml (~> 0.5)
+ multipart-post (~> 2.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ public_suffix (~> 2.0.0)
+ rubyzip (>= 1.2.2, < 2.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ slack-notifier (>= 2.0.0, < 3.0.0)
+ terminal-notifier (>= 1.6.2, < 2.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.6.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ fastlane-plugin-clean_testflight_testers (0.2.0)
+ fastlane-plugin-souyuz (0.8.1)
+ souyuz (>= 0.8.1)
+ fastlane-plugin-xamarin (0.6.3)
+ gh_inspector (1.1.3)
+ google-api-client (0.23.9)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.5, < 0.7.0)
+ httpclient (>= 2.8.1, < 3.0)
+ mime-types (~> 3.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ signet (~> 0.9)
+ google-cloud-core (1.3.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-env (1.0.5)
+ faraday (~> 0.11)
+ google-cloud-storage (1.16.0)
+ digest-crc (~> 0.4)
+ google-api-client (~> 0.23)
+ google-cloud-core (~> 1.2)
+ googleauth (>= 0.6.2, < 0.10.0)
+ googleauth (0.6.7)
+ faraday (~> 0.12)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (~> 0.7)
+ highline (1.7.10)
+ http-cookie (1.0.3)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ json (2.2.0)
+ jwt (2.1.0)
+ memoist (0.16.0)
+ mime-types (3.2.2)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2018.0812)
+ mini_magick (4.5.1)
+ mini_portile2 (2.4.0)
+ multi_json (1.13.1)
+ multi_xml (0.6.0)
+ multipart-post (2.0.0)
+ nanaimo (0.2.6)
+ naturally (2.2.0)
+ nokogiri (1.10.1)
+ mini_portile2 (~> 2.4.0)
+ os (1.0.0)
+ plist (3.5.0)
+ public_suffix (2.0.5)
+ representable (3.0.4)
+ declarative (< 0.1.0)
+ declarative-option (< 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rouge (2.0.7)
+ rubyzip (1.2.2)
+ security (0.1.3)
+ signet (0.11.0)
+ addressable (~> 2.3)
+ faraday (~> 0.9)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.5)
+ CFPropertyList
+ naturally
+ slack-notifier (2.3.2)
+ souyuz (0.8.1)
+ fastlane (>= 2.29.0)
+ highline (~> 1.7)
+ nokogiri (~> 1.7)
+ terminal-notifier (1.8.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ tty-cursor (0.6.1)
+ tty-screen (0.6.5)
+ tty-spinner (0.9.0)
+ tty-cursor (~> 0.6.0)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.7.5)
+ unicode-display_width (1.4.1)
+ word_wrap (1.0.0)
+ xcodeproj (1.8.1)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.2.6)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.0)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ fastlane
+ fastlane-plugin-clean_testflight_testers
+ fastlane-plugin-souyuz
+ fastlane-plugin-xamarin
+
+BUNDLED WITH
+ 2.0.1
diff --git a/README.md b/README.md
index a54b28b74a..abddb1faa1 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,24 @@
-# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
+# osu!
+
+[](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](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.
-# Status
+## Status
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
-# Requirements
+## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
-# Running osu!
+## Running osu!
-## Releases
+### Releases
If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled.
@@ -26,26 +28,22 @@ If you are not interested in developing the game, please head over to the [relea
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
-## Downloading the source code
+### Downloading the source code
Clone the repository **including submodules**:
```shell
-git clone --recurse-submodules https://github.com/ppy/osu
+git clone https://github.com/ppy/osu
cd osu
```
-> If you forgot the `--recurse-submodules` option, run this command inside the `osu` directory:
->
-> `git submodule update --init --recursive`
-
To update the source code to the latest commit, run the following command inside the `osu` directory:
```shell
-git pull --recurse-submodules
+git pull
```
-## Building
+### Building
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided below.
@@ -61,7 +59,7 @@ 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`.
-### A note for Linux users
+#### A note for Linux users
On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`.
@@ -73,11 +71,15 @@ For example, you can run osu! with the following command:
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop
```
-## Code analysis
+### Testing with resource/framework modifications
-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). Alternative, you can install resharper or use rider to get inline support in your IDE of choice.
+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.
-# Contributing
+### 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.
+
+## Contributing
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
@@ -87,7 +89,7 @@ Contributions can be made via pull requests to this repository. We hope to credi
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
-# Licence
+## Licence
The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
diff --git a/build.ps1 b/build.ps1
index 9968673c90..c6a0bf6d4a 100644
--- a/build.ps1
+++ b/build.ps1
@@ -41,27 +41,28 @@ Param(
[switch]$ShowDescription,
[Alias("WhatIf", "Noop")]
[switch]$DryRun,
- [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
+ [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
[string[]]$ScriptArgs
)
Write-Host "Preparing to run build script..."
# Determine the script root for resolving other paths.
-if(!$PSScriptRoot){
+if(!$PSScriptRoot) {
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
# Resolve the paths for resources used for debugging.
-$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
-$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj"
+$BUILD_DIR = Join-Path $PSScriptRoot "build"
+$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
+$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
# Install the required tools locally.
Write-Host "Restoring cake tools..."
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
# Find the Cake executable
-$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName
+$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
# Build Cake arguments
$cakeArguments = @("$Script");
@@ -75,5 +76,7 @@ $cakeArguments += $ScriptArgs
# Start Cake
Write-Host "Running build script..."
+Push-Location -Path $BUILD_DIR
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
+Pop-Location
exit $LASTEXITCODE
diff --git a/build.sh b/build.sh
index caf1702f41..8f1ef5b455 100755
--- a/build.sh
+++ b/build.sh
@@ -6,12 +6,13 @@
echo "Preparing to run build script..."
+cd build
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
TOOLS_DIR=$SCRIPT_DIR/tools
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
SCRIPT="build.cake"
-CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj"
+CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
# Parse arguments.
CAKE_ARGUMENTS=()
diff --git a/build.cake b/build/build.cake
similarity index 75%
rename from build.cake
rename to build/build.cake
index bc7dfafb8c..81deeb3bc7 100644
--- a/build.cake
+++ b/build/build.cake
@@ -1,6 +1,7 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.21"
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
+var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
///////////////////////////////////////////////////////////////////////////////
// ARGUMENTS
@@ -9,30 +10,24 @@
var target = Argument("target", "Build");
var configuration = Argument("configuration", "Release");
-var osuSolution = new FilePath("./osu.sln");
+var rootDirectory = new DirectoryPath("..");
+var solution = rootDirectory.CombineWithFilePath("osu.sln");
///////////////////////////////////////////////////////////////////////////////
// TASKS
///////////////////////////////////////////////////////////////////////////////
-Task("Restore")
- .Does(() => {
- DotNetCoreRestore(osuSolution.FullPath);
- });
-
Task("Compile")
- .IsDependentOn("Restore")
.Does(() => {
- DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings {
+ DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings {
Configuration = configuration,
- NoRestore = true,
});
});
Task("Test")
.IsDependentOn("Compile")
.Does(() => {
- var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll");
+ var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll");
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
@@ -46,9 +41,7 @@ Task("InspectCode")
.WithCriteria(IsRunningOnWindows())
.IsDependentOn("Compile")
.Does(() => {
- var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
-
- InspectCode(osuSolution, new InspectCodeSettings {
+ InspectCode(solution, new InspectCodeSettings {
CachesHome = "inspectcode",
OutputFile = "inspectcodereport.xml",
});
@@ -59,7 +52,7 @@ Task("InspectCode")
Task("CodeFileSanity")
.Does(() => {
ValidateCodeSanity(new ValidateCodeSanitySettings {
- RootDirectory = ".",
+ RootDirectory = rootDirectory.FullPath,
IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
});
});
diff --git a/tools/cakebuild.csproj b/build/cakebuild.csproj
similarity index 100%
rename from tools/cakebuild.csproj
rename to build/cakebuild.csproj
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000000..083de66985
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,2 @@
+app_identifier("sh.ppy.osulazer") # The bundle identifier of your app
+apple_id("apple-dev@ppy.sh") # Your Apple email address
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000000..3f64bcdf19
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,65 @@
+update_fastlane
+
+default_platform(:ios)
+
+platform :ios do
+ lane :testflight_prune_dry do
+ clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ end
+
+ # Specify a custom number for what's "inactive"
+ lane :testflight_prune do
+ clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months
+ end
+
+ lane :update_version do |options|
+ options[:plist_path] = '../osu.iOS/Info.plist'
+ app_version(options)
+ end
+
+ desc 'Deploy to testflight'
+ lane :beta do |options|
+ update_version(options)
+
+ provision(
+ type: 'appstore'
+ )
+
+ build(
+ build_configuration: 'Release',
+ build_platform: 'iPhone'
+ )
+
+ client = HTTPClient.new
+ changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw'
+ changelog.gsub!('$BUILD_ID', options[:build])
+
+ pilot(
+ wait_processing_interval: 900,
+ changelog: changelog,
+ ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
+ )
+ end
+
+ desc 'Compile the project'
+ lane :build do
+ nuget_restore(
+ project_path: 'osu.iOS.sln'
+ )
+
+ souyuz(
+ platform: "ios",
+ build_target: "osu_iOS",
+ plist_path: "../osu.iOS/Info.plist"
+ )
+ end
+
+ desc 'Install provisioning profiles using match'
+ lane :provision do |options|
+ if Helper.is_ci?
+ options[:readonly] = true
+ end
+
+ match(options)
+ end
+end
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
new file mode 100644
index 0000000000..40c974b09e
--- /dev/null
+++ b/fastlane/Matchfile
@@ -0,0 +1 @@
+git_url('https://github.com/peppy/apple-certificates')
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
new file mode 100644
index 0000000000..9f4f47f213
--- /dev/null
+++ b/fastlane/Pluginfile
@@ -0,0 +1,7 @@
+# Autogenerated by fastlane
+#
+# Ensure this file is checked in to source control!
+
+gem 'fastlane-plugin-clean_testflight_testers'
+gem 'fastlane-plugin-souyuz'
+gem 'fastlane-plugin-xamarin'
diff --git a/fastlane/README.md b/fastlane/README.md
new file mode 100644
index 0000000000..53bbc62cae
--- /dev/null
+++ b/fastlane/README.md
@@ -0,0 +1,54 @@
+fastlane documentation
+================
+# Installation
+
+Make sure you have the latest version of the Xcode command line tools installed:
+
+```
+xcode-select --install
+```
+
+Install _fastlane_ using
+```
+[sudo] gem install fastlane -NV
+```
+or alternatively using `brew cask install fastlane`
+
+# Available Actions
+## iOS
+### ios testflight_prune_dry
+```
+fastlane ios testflight_prune_dry
+```
+
+### ios testflight_prune
+```
+fastlane ios testflight_prune
+```
+
+### ios update_version
+```
+fastlane ios update_version
+```
+
+### ios beta
+```
+fastlane ios beta
+```
+Deploy to testflight
+### ios build
+```
+fastlane ios build
+```
+Compile the project
+### ios provision
+```
+fastlane ios provision
+```
+Install provisioning profiles using match
+
+----
+
+This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
+More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
+The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/osu-resources b/osu-resources
deleted file mode 160000
index 9880089b4e..0000000000
--- a/osu-resources
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 9880089b4e8fcd78d68f30c8a40d43bf8dccca86
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index c4c0cc1fdd..7e5b003f03 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -16,7 +16,6 @@ using osu.Desktop.Updater;
using osu.Framework;
using osu.Framework.Platform.Windows;
using osu.Framework.Screens;
-using osu.Game.Screens;
using osu.Game.Screens.Menu;
namespace osu.Desktop
@@ -63,9 +62,10 @@ namespace osu.Desktop
}
}
- protected override void ScreenChanged(OsuScreen current, Screen newScreen)
+ protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
{
- base.ScreenChanged(current, newScreen);
+ base.ScreenChanged(lastScreen, newScreen);
+
switch (newScreen)
{
case Intro _:
@@ -83,8 +83,7 @@ namespace osu.Desktop
public override void SetHost(GameHost host)
{
base.SetHost(host);
- var desktopWindow = host.Window as DesktopGameWindow;
- if (desktopWindow != null)
+ if (host.Window is DesktopGameWindow desktopWindow)
{
desktopWindow.CursorState |= CursorState.Hidden;
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 5b67d528ae..b8a0e337b6 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -60,7 +60,7 @@ namespace osu.Desktop.Overlays
{
new OsuSpriteText
{
- Font = @"Exo2.0-Bold",
+ Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = game.Name
},
new OsuSpriteText
@@ -74,9 +74,8 @@ namespace osu.Desktop.Overlays
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- TextSize = 12,
+ Font = OsuFont.Numeric.With(size: 12),
Colour = colours.Yellow,
- Font = @"Venera",
Text = @"Development Build"
},
new Sprite
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 4f0ae3d65d..66db439c82 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -22,14 +22,13 @@
-
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..39fe3dac25
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Catch.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..44817c1304
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Rulesets.Catch.Tests.iOS
+{
+ public class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..5115746cbb
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Rulesets.Catch.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Rulesets-Catch-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
new file mode 100644
index 0000000000..37e7c45a4e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -0,0 +1,45 @@
+
+
+
+
+ Debug
+ iPhoneSimulator
+ {4004C7B7-1A62-43F1-9DF2-52450FA67E70}
+ Exe
+ osu.Game.Rulesets.Catch.Tests
+ osu.Game.Rulesets.Catch.Tests.iOS
+
+
+
+
+
+
+ libbass.a
+ PreserveNewest
+
+
+ libbass_fx.a
+ PreserveNewest
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}
+ osu.Game.Rulesets.Catch
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index fd30a8d4ae..7f85d4ccce 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("basic")]
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
+ [TestCase("slider")]
public new void Test(string name)
{
base.Test(name);
@@ -33,13 +34,16 @@ namespace osu.Game.Rulesets.Catch.Tests
case JuiceStream stream:
foreach (var nested in stream.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
+
break;
case BananaShower shower:
foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
+
break;
default:
yield return new ConvertValue((CatchHitObject)hitObject);
+
break;
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
new file mode 100644
index 0000000000..51fe0b035d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
+
+ [TestCase(4.2058561036909863d, "diffcalc-test")]
+ public void Test(double expected, string name)
+ => base.Test(expected, name);
+
+ protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
+
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
index 9319fb3dfb..fbb2db33b0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
@@ -13,7 +13,7 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestCaseAutoJuiceStream : TestCasePlayer
+ public class TestCaseAutoJuiceStream : PlayerTestCase
{
public TestCaseAutoJuiceStream()
: base(new CatchRuleset())
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
index 9e1c44ba40..d413b53d17 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
@@ -8,11 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer
+ public class TestCaseBananaShower : PlayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableBananaShower),
typeof(CatchRuleset),
- typeof(CatchRulesetContainer),
+ typeof(DrawableCatchRuleset),
};
public TestCaseBananaShower()
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
index 030c52afea..5b242d05d7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
@@ -2,13 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
+ public class TestCaseCatchPlayer : PlayerTestCase
{
- public TestCaseCatchPlayer() : base(new CatchRuleset())
+ public TestCaseCatchPlayer()
+ : base(new CatchRuleset())
{
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
index 1e3d60d968..5a16a23a4e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
@@ -4,11 +4,12 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
+ public class TestCaseCatchStacker : PlayerTestCase
{
public TestCaseCatchStacker()
: base(new CatchRuleset())
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
index 0851fbed87..a7e7f0ab14 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
@@ -2,26 +2,45 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer
+ public class TestCaseHyperDash : PlayerTestCase
{
public TestCaseHyperDash()
: base(new CatchRuleset())
{
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
+ }
+
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
- var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = ruleset.RulesetInfo,
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
+ }
+ };
+
+ // Should produce a hyper-dash
+ beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true });
+ beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
for (int i = 0; i < 512; i++)
if (i % 5 < 3)
- beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
+ beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index feab3ed81c..3f8b3bf086 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 2932afa9fe..78b5a510b2 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
}
+
break;
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
@@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
rng.Next(); // osu!stable retrieved a random droplet rotation
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
+
break;
}
}
@@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
- double timeToNext = nextObject.StartTime - currentObject.StartTime;
+ double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs
index 285b90b0ce..021d7a7efe 100644
--- a/osu.Game.Rulesets.Catch/CatchInputManager.cs
+++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs
@@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
{
[Description("Move left")]
MoveLeft,
+
[Description("Move right")]
MoveRight,
+
[Description("Engage dash")]
Dash,
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index a69070e93e..5140135f80 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
@@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Catch
new MultiMod(new CatchModAutoplay(), new ModCinema()),
new CatchModRelax(),
};
+ case ModType.Fun:
+ return new Mod[]
+ {
+ new MultiMod(new ModWindUp(), new ModWindDown())
+ };
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
index c6518cbf8a..75f5b18607 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
public double ApproachRate;
public int MaxCombo;
-
- public CatchDifficultyAttributes(Mod[] mods, double starRating)
- : base(mods, starRating)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index a0b813478d..b4998347f4 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,148 +1,102 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Catch.Difficulty.Skills;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
- ///
- /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
- /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
- /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
- ///
- private const double strain_step = 750;
-
- ///
- /// The weighting of each strain value decays to this number * it's previous value
- ///
- private const double decay_weight = 0.94;
-
private const double star_scaling_factor = 0.145;
+ protected override int SectionLength => 750;
+
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
- if (!beatmap.HitObjects.Any())
- return new CatchDifficultyAttributes(mods, 0);
+ if (beatmap.HitObjects.Count == 0)
+ return new CatchDifficultyAttributes { Mods = mods };
- var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
- float halfCatchWidth = catcher.CatchWidth * 0.5f;
+ // this is the same as osu!, so there's potential to share the implementation... maybe
+ double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
- var difficultyHitObjects = new List();
-
- foreach (var hitObject in beatmap.HitObjects)
+ return new CatchDifficultyAttributes
{
+ StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
+ Mods = mods,
+ ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
+ MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet))
+ };
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
+ {
+ float halfCatchWidth;
+
+ using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty))
+ {
+ halfCatchWidth = catcher.CatchWidth * 0.5f;
+ halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
+ }
+
+ CatchHitObject lastObject = null;
+
+ foreach (var hitObject in beatmap.HitObjects.OfType())
+ {
+ if (lastObject == null)
+ {
+ lastObject = hitObject;
+ continue;
+ }
+
switch (hitObject)
{
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
case Fruit fruit:
- difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth));
+ yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
+
+ lastObject = hitObject;
break;
case JuiceStream _:
- difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
+ foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)))
+ {
+ yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
+
+ lastObject = nested;
+ }
+
break;
}
}
-
- difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
-
- if (!calculateStrainValues(difficultyHitObjects, timeRate))
- return new CatchDifficultyAttributes(mods, 0);
-
- // this is the same as osu!, so there's potential to share the implementation... maybe
- double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
- double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
-
- return new CatchDifficultyAttributes(mods, starRating)
- {
- ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
- MaxCombo = difficultyHitObjects.Count
- };
}
- private bool calculateStrainValues(List objects, double timeRate)
+ protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
{
- CatchDifficultyHitObject lastObject = null;
+ new Movement(),
+ };
- if (!objects.Any()) return false;
-
- // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
- foreach (var currentObject in objects)
- {
- if (lastObject != null)
- currentObject.CalculateStrains(lastObject, timeRate);
-
- lastObject = currentObject;
- }
-
- return true;
- }
-
- private double calculateDifficulty(List objects, double timeRate)
+ protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
- // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
- double actualStrainStep = strain_step * timeRate;
-
- // Find the highest strain value within each strain step
- var highestStrains = new List();
- double intervalEndTime = actualStrainStep;
- double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
-
- CatchDifficultyHitObject previousHitObject = null;
- foreach (CatchDifficultyHitObject hitObject in objects)
- {
- // While we are beyond the current interval push the currently available maximum to our strain list
- while (hitObject.BaseHitObject.StartTime > intervalEndTime)
- {
- highestStrains.Add(maximumStrain);
-
- // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
- // until the beginning of the next interval.
- if (previousHitObject == null)
- {
- maximumStrain = 0;
- }
- else
- {
- double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
- maximumStrain = previousHitObject.Strain * decay;
- }
-
- // Go to the next time interval
- intervalEndTime += actualStrainStep;
- }
-
- // Obtain maximum strain
- maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
-
- previousHitObject = hitObject;
- }
-
- // Build the weighted sum over the highest strains for each interval
- double difficulty = 0;
- double weight = 1;
- highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
-
- foreach (double strain in highestStrains)
- {
- difficulty += weight * strain;
- weight *= decay_weight;
- }
-
- return difficulty;
- }
+ new CatchModDoubleTime(),
+ new CatchModHalfTime(),
+ new CatchModHardRock(),
+ new CatchModEasy(),
+ };
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
deleted file mode 100644
index fb16e117b1..0000000000
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Difficulty
-{
- public class CatchDifficultyHitObject
- {
- internal static readonly double DECAY_BASE = 0.20;
- private const float normalized_hitobject_radius = 41.0f;
- private const float absolute_player_positioning_error = 16f;
- private readonly float playerPositioningError;
-
- internal CatchHitObject BaseHitObject;
-
- ///
- /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
- ///
- internal double Strain = 1;
-
- ///
- /// This is required to keep track of lazy player movement (always moving only as far as necessary)
- /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
- ///
- internal float PlayerPositionOffset;
- internal float LastMovement;
-
- internal float NormalizedPosition;
- internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
-
- internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
- {
- BaseHitObject = baseHitObject;
-
- // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
- float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
-
- playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
- NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
- }
-
- private const double direction_change_bonus = 12.5;
- internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
- {
- // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
- // See Taiko feedback thread.
- double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
- double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
-
- // Update new position with lazy movement.
- PlayerPositionOffset =
- MathHelper.Clamp(
- previousHitObject.ActualNormalizedPosition,
- NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
- NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
- - NormalizedPosition; // Subtract HitObject position to obtain offset
-
- LastMovement = DistanceTo(previousHitObject);
- double addition = spacingWeight(LastMovement);
-
- if (NormalizedPosition < previousHitObject.NormalizedPosition)
- {
- LastMovement = -LastMovement;
- }
-
- CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
-
- double additionBonus = 0;
- double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
-
- // Direction changes give an extra point!
- if (Math.Abs(LastMovement) > 0.1)
- {
- if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
- {
- double bonus = direction_change_bonus / sqrtTime;
-
- // Weight bonus by how
- double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
-
- // We want time to play a role twice here!
- addition += bonus * bonusFactor;
-
- // Bonus for tougher direction switches and "almost" hyperdashes at this point
- if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
- {
- additionBonus += 0.3 * bonusFactor;
- }
- }
-
- // Base bonus for every movement, giving some weight to streams.
- addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
- }
-
- // Bonus for "almost" hyperdashes at corner points
- if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
- {
- if (!previousHitCircle.HyperDash)
- {
- additionBonus += 1.0;
- }
- else
- {
- // After a hyperdash we ARE in the correct position. Always!
- PlayerPositionOffset = 0;
- }
-
- addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
- }
-
- addition *= 850.0 / Math.Max(timeElapsed, 25);
-
- Strain = previousHitObject.Strain * decay + addition;
- }
-
- private static double spacingWeight(float distance)
- {
- return Math.Pow(distance, 1.3) / 500;
- }
-
- internal float DistanceTo(CatchDifficultyHitObject other)
- {
- return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
new file mode 100644
index 0000000000..24e526ed19
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
+{
+ public class CatchDifficultyHitObject : DifficultyHitObject
+ {
+ private const float normalized_hitobject_radius = 41.0f;
+
+ public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
+
+ public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
+
+ public readonly float NormalizedPosition;
+ public readonly float LastNormalizedPosition;
+
+ ///
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms.
+ ///
+ public readonly double StrainTime;
+
+ public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
+ : base(hitObject, lastObject, clockRate)
+ {
+ // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
+ var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
+
+ NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
+ LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
+
+ // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
+ StrainTime = Math.Max(25, DeltaTime);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
new file mode 100644
index 0000000000..d146153294
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Difficulty.Skills
+{
+ public class Movement : Skill
+ {
+ private const float absolute_player_positioning_error = 16f;
+ private const float normalized_hitobject_radius = 41.0f;
+ private const double direction_change_bonus = 12.5;
+
+ protected override double SkillMultiplier => 850;
+ protected override double StrainDecayBase => 0.2;
+
+ protected override double DecayWeight => 0.94;
+
+ private float? lastPlayerPosition;
+ private float lastDistanceMoved;
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ var catchCurrent = (CatchDifficultyHitObject)current;
+
+ if (lastPlayerPosition == null)
+ lastPlayerPosition = catchCurrent.LastNormalizedPosition;
+
+ float playerPosition = MathHelper.Clamp(
+ lastPlayerPosition.Value,
+ catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
+ catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
+ );
+
+ float distanceMoved = playerPosition - lastPlayerPosition.Value;
+
+ double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
+ double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
+
+ double bonus = 0;
+
+ // Direction changes give an extra point!
+ if (Math.Abs(distanceMoved) > 0.1)
+ {
+ if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
+ {
+ double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
+
+ distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
+
+ // Bonus for tougher direction switches and "almost" hyperdashes at this point
+ if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
+ bonus = 0.3 * bonusFactor;
+ }
+
+ // Base bonus for every movement, giving some weight to streams.
+ distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
+ }
+
+ // Bonus for "almost" hyperdashes at corner points
+ if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
+ {
+ if (!catchCurrent.LastObject.HyperDash)
+ bonus += 1.0;
+ else
+ {
+ // After a hyperdash we ARE in the correct position. Always!
+ playerPosition = catchCurrent.NormalizedPosition;
+ }
+
+ distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
+ }
+
+ lastPlayerPosition = playerPosition;
+ lastDistanceMoved = distanceMoved;
+
+ return distanceAddition / catchCurrent.StrainTime;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index 2bd2c9766f..b3605b013b 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = _x ^ _x << 11;
+ uint t = _x ^ (_x << 11);
_x = _y;
_y = _z;
_z = _w;
- return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
+ return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
}
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
index 612df5bde5..692e63fa69 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModAutoplay : ModAutoplay
{
- protected override Score CreateReplayScore(Beatmap beatmap) => new Score
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index 4f5d7abfd4..71268d899d 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
@@ -20,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods
private CatchPlayfield playfield;
- public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- playfield = (CatchPlayfield)rulesetContainer.Playfield;
- base.ApplyToRulesetContainer(rulesetContainer);
+ playfield = (CatchPlayfield)drawableRuleset.Playfield;
+ base.ApplyToDrawableRuleset(drawableRuleset);
}
private class CatchFlashlight : Flashlight
@@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Mods
return default_flashlight_size;
}
- protected override void OnComboChange(int newCombo)
+ protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 0cfa3e98f7..060e51e31d 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -15,77 +15,107 @@ namespace osu.Game.Rulesets.Catch.Mods
public override double ScoreMultiplier => 1.12;
public override bool Ranked => true;
- private float lastStartX;
- private int lastStartTime;
+ private float? lastPosition;
+ private double lastStartTime;
public void ApplyToHitObject(HitObject hitObject)
{
+ if (hitObject is JuiceStream stream)
+ {
+ lastPosition = stream.EndX;
+ lastStartTime = stream.EndTime;
+ return;
+ }
+
+ if (!(hitObject is Fruit))
+ return;
+
var catchObject = (CatchHitObject)hitObject;
float position = catchObject.X;
- int startTime = (int)hitObject.StartTime;
+ double startTime = hitObject.StartTime;
- if (lastStartX == 0)
+ if (lastPosition == null)
{
- lastStartX = position;
+ lastPosition = position;
lastStartTime = startTime;
+
return;
}
- float diff = lastStartX - position;
- int timeDiff = startTime - lastStartTime;
+ float positionDiff = position - lastPosition.Value;
+ double timeDiff = startTime - lastStartTime;
if (timeDiff > 1000)
{
- lastStartX = position;
+ lastPosition = position;
lastStartTime = startTime;
return;
}
- if (diff == 0)
+ if (positionDiff == 0)
{
- bool right = RNG.NextBool();
-
- float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH;
-
- if (right)
- {
- if (position + rand <= 1)
- position += rand;
- else
- position -= rand;
- }
- else
- {
- if (position - rand >= 0)
- position -= rand;
- else
- position += rand;
- }
-
+ applyRandomOffset(ref position, timeDiff / 4d);
catchObject.X = position;
-
return;
}
- if (Math.Abs(diff) < timeDiff / 3d)
- {
- if (diff > 0)
- {
- if (position - diff > 0)
- position -= diff;
- }
- else
- {
- if (position - diff < 1)
- position -= diff;
- }
- }
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ applyOffset(ref position, positionDiff);
catchObject.X = position;
- lastStartX = position;
+ lastPosition = position;
lastStartTime = startTime;
}
+
+ ///
+ /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The maximum offset, cannot exceed 20px.
+ private void applyRandomOffset(ref float position, double maxOffset)
+ {
+ bool right = RNG.NextBool();
+ float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
+
+ if (right)
+ {
+ // Clamp to the right bound
+ if (position + rand <= 1)
+ position += rand;
+ else
+ position -= rand;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position - rand >= 0)
+ position -= rand;
+ else
+ position += rand;
+ }
+ }
+
+ ///
+ /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The amount to offset by.
+ private void applyOffset(ref float position, float amount)
+ {
+ if (amount > 0)
+ {
+ // Clamp to the right bound
+ if (position + amount < 1)
+ position += amount;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position + amount > 0)
+ position += amount;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 922d6bf3e9..294fd97d59 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
- AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : Color4.White);
+ AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
private const float preempt = 1000;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index c80dacde93..8fed8eb4cd 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index d5adbee8aa..2e18c5f2ad 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
}
private Color4 accentColour;
+
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
accentColour = value;
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index ef7573c70b..2adc156efd 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
@@ -25,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Velocity;
public double TickDistance;
+ ///
+ /// The length of one span of this .
+ ///
+ public double SpanDuration => Duration / this.SpanCount();
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -41,93 +45,67 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
- createTicks();
- }
- private void createTicks()
- {
- if (TickDistance == 0)
- return;
-
- var length = Path.Distance;
- var tickDistance = Math.Min(TickDistance, length);
- var spanDuration = length / Velocity;
-
- var minDistanceFromEnd = Velocity * 0.01;
-
- AddNested(new Fruit
+ var tickSamples = Samples.Select(s => new SampleInfo
{
- Samples = Samples,
- StartTime = StartTime,
- X = X
- });
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }).ToList();
- double lastDropletTime = StartTime;
+ SliderEventDescriptor? lastEvent = null;
- for (int span = 0; span < this.SpanCount(); span++)
+ foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
- var spanStartTime = StartTime + span * spanDuration;
- var reversed = span % 2 == 1;
-
- for (double d = 0; d <= length; d += tickDistance)
+ // generate tiny droplets since the last point
+ if (lastEvent != null)
{
- var timeProgress = d / length;
- var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
+ double sinceLastTick = e.Time - lastEvent.Value.Time;
- double time = spanStartTime + timeProgress * spanDuration;
-
- if (LegacyLastTickOffset != null)
+ if (sinceLastTick > 80)
{
- // If we're the last tick, apply the legacy offset
- if (span == this.SpanCount() - 1 && d + tickDistance > length)
- time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
- }
+ double timeBetweenTiny = sinceLastTick;
+ while (timeBetweenTiny > 100)
+ timeBetweenTiny /= 2;
- double tinyTickInterval = time - lastDropletTime;
- while (tinyTickInterval > 100)
- tinyTickInterval /= 2;
-
- for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
- {
- double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
-
- AddNested(new TinyDroplet
+ for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
{
- StartTime = t,
- X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
+ AddNested(new TinyDroplet
{
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
+ Samples = tickSamples,
+ StartTime = t + lastEvent.Value.Time,
+ X = X + Path.PositionAt(
+ lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
+ });
+ }
}
-
- if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
- {
- AddNested(new Droplet
- {
- StartTime = time,
- X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
- {
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
- }
-
- lastDropletTime = time;
}
- AddNested(new Fruit
+ // this also includes LegacyLastTick and this is used for TinyDroplet generation above.
+ // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied.
+ lastEvent = e;
+
+ switch (e.Type)
{
- Samples = Samples,
- StartTime = spanStartTime + spanDuration,
- X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
- });
+ case SliderEventType.Tick:
+ AddNested(new Droplet
+ {
+ Samples = tickSamples,
+ StartTime = e.Time,
+ X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
+ });
+ break;
+ case SliderEventType.Head:
+ case SliderEventType.Tail:
+ case SliderEventType.Repeat:
+ AddNested(new Fruit
+ {
+ Samples = Samples,
+ StartTime = e.Time,
+ X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
+ });
+ break;
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index d4b6baf9b4..dba76eef49 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -9,3 +9,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.iOS")]
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 2fd05483ef..daa3f61de3 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -6,17 +6,20 @@ using System.Linq;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays
{
- internal class CatchAutoGenerator : AutoGenerator
+ internal class CatchAutoGenerator : AutoGenerator
{
public const double RELEASE_DELAY = 20;
- public CatchAutoGenerator(Beatmap beatmap)
+ public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
+
+ public CatchAutoGenerator(IBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay();
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu
new file mode 100644
index 0000000000..ebad654404
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu
@@ -0,0 +1,138 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.3
+Mode: 2
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+34500,-50,4,2,1,50,0,0
+
+[HitObjects]
+// fruits spaced 1/1 beat apart
+32,128,0,5,0,0:0:0:0:
+96,128,500,1,0,0:0:0:0:
+160,128,1000,1,0,0:0:0:0:
+224,128,1500,1,0,0:0:0:0:
+288,128,2000,1,0,0:0:0:0:
+352,128,2500,1,0,0:0:0:0:
+416,128,3000,1,0,0:0:0:0:
+480,128,3500,1,0,0:0:0:0:
+
+// fruits spaced 1/2 beat apart
+32,160,4500,1,0,0:0:0:0:
+64,160,4750,1,0,0:0:0:0:
+96,160,5000,1,0,0:0:0:0:
+128,160,5250,1,0,0:0:0:0:
+160,160,5500,1,0,0:0:0:0:
+192,160,5750,1,0,0:0:0:0:
+224,160,6000,1,0,0:0:0:0:
+256,160,6250,1,0,0:0:0:0:
+288,160,6500,1,0,0:0:0:0:
+
+// fruits spaced 1/4 beat apart
+96,128,7500,1,0,0:0:0:0:
+128,128,7625,1,0,0:0:0:0:
+160,128,7750,1,0,0:0:0:0:
+192,128,7875,1,0,0:0:0:0:
+224,128,8000,1,0,0:0:0:0:
+256,128,8125,1,0,0:0:0:0:
+288,128,8250,1,0,0:0:0:0:
+320,128,8375,1,0,0:0:0:0:
+352,128,8500,1,0,0:0:0:0:
+
+// fruit hyperdashes, spaced 1/2 beat apart
+32,160,9500,1,0,0:0:0:0:
+480,160,9750,1,0,0:0:0:0:
+32,160,10000,1,0,0:0:0:0:
+480,160,10250,1,0,0:0:0:0:
+32,160,10500,1,0,0:0:0:0:
+480,160,10750,1,0,0:0:0:0:
+32,160,11000,1,0,0:0:0:0:
+
+// fruit hyperdashes, spaced 1/4 beat apart
+32,192,12000,1,0,0:0:0:0:
+480,192,12125,1,0,0:0:0:0:
+32,192,12250,1,0,0:0:0:0:
+480,192,12375,1,0,0:0:0:0:
+32,192,12500,1,0,0:0:0:0:
+480,192,12625,1,0,0:0:0:0:
+32,192,12750,1,0,0:0:0:0:
+480,192,12875,1,0,0:0:0:0:
+32,192,13000,1,0,0:0:0:0:
+
+// stream + hyperdash + stream, spaced 1/4 beat apart
+32,192,14000,1,0,0:0:0:0:
+64,192,14125,1,0,0:0:0:0:
+96,192,14250,1,0,0:0:0:0:
+128,192,14375,1,0,0:0:0:0:
+480,192,14500,1,0,0:0:0:0:
+448,192,14625,1,0,0:0:0:0:
+416,192,14750,1,0,0:0:0:0:
+384,192,14875,1,0,0:0:0:0:
+32,192,15000,1,0,0:0:0:0:
+
+// basic sliders
+32,192,16000,2,0,L|192:192,1,160
+224,192,17000,2,0,L|384:192,1,160
+416,192,17875,2,0,L|480:192,1,40
+
+// slider hyperdashes, spaced 1/4 beat apart
+32,192,19000,2,0,L|128:192,1,80
+480,192,19375,2,0,L|384:192,1,80
+352,192,19750,2,0,L|256:192,1,80
+0,192,20125,2,0,L|128:192,1,120
+
+// stream + slider hyperdashes, spaced 1/4 beat apart
+32,192,21500,1,0,0:0:0:0:
+64,192,21625,1,0,0:0:0:0:
+96,192,21750,1,0,0:0:0:0:
+512,192,21875,2,0,L|320:192,1,160
+320,192,22500,1,0,0:0:0:0:
+288,192,22625,1,0,0:0:0:0:
+256,192,22750,1,0,0:0:0:0:
+0,192,22875,2,0,L|64:192,1,40
+
+// streams, spaced 1/4 beat apart
+64,192,24000,1,0,0:0:0:0:
+160,192,24125,1,0,0:0:0:0:
+64,192,24250,1,0,0:0:0:0:
+160,192,24375,1,0,0:0:0:0:
+64,192,24500,1,0,0:0:0:0:
+160,192,24625,1,0,0:0:0:0:
+64,192,24750,1,0,0:0:0:0:
+160,192,24875,1,0,0:0:0:0:
+64,192,25000,1,0,0:0:0:0:
+160,192,25125,1,0,0:0:0:0:
+64,192,25250,1,0,0:0:0:0:
+160,192,25375,1,0,0:0:0:0:
+64,192,25500,1,0,0:0:0:0:
+
+// stream + spinner combo, spaced 1/4 beat apart
+256,192,26500,12,0,27000,0:0:0:0:
+128,192,27250,5,0,0:0:0:0:
+128,192,27375,1,0,0:0:0:0:
+160,192,27500,1,0,0:0:0:0:
+192,192,27625,1,0,0:0:0:0:
+256,192,27750,12,0,28500,0:0:0:0:
+192,192,28625,5,0,0:0:0:0:
+224,192,28750,1,0,0:0:0:0:
+256,192,28875,1,0,0:0:0:0:
+256,192,29000,1,0,0:0:0:0:
+256,192,29125,12,0,29500,0:0:0:0:
+
+// long slow slider
+0,192,30500,6,0,B|480:192|480:192|0:192,2,960
+
+// long fast slider
+0,192,37500,6,0,B|480:192|480:192|0:192,2,960
+
+// long hyperdash slider
+0,192,41500,2,0,P|544:192|544:192,5,480
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
new file mode 100644
index 0000000000..58c52b6867
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
@@ -0,0 +1 @@
+{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu
new file mode 100644
index 0000000000..d48b2d7769
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+Mode: 2
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:2
+OverallDifficulty:4
+ApproachRate:4
+SliderMultiplier:0.9
+SliderTickRate:1
+
+[TimingPoints]
+35.4473684210527,638.298947368422,4,2,1,60,1,0
+
+[HitObjects]
+320,176,19184,2,8,P|384:168|368:232,1,150
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 5e33dd59b1..af614f95d0 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(RulesetContainer rulesetContainer)
- : base(rulesetContainer)
+ public CatchScoreProcessor(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
{
}
@@ -43,6 +43,6 @@ namespace osu.Game.Rulesets.Catch.Scoring
Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
}
- protected override HitWindows CreateHitWindows() => new CatchHitWindows();
+ public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index d79f106310..c6dd0a86a0 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
- public const float CATCHER_SIZE = 100;
+ public const float CATCHER_SIZE = 106.75f;
protected internal readonly Catcher MovableCatcher;
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
public Container ExplodingFruitTarget
{
- set { MovableCatcher.ExplodingFruitTarget = value; }
+ set => MovableCatcher.ExplodingFruitTarget = value;
}
public CatcherArea(BeatmapDifficulty difficulty = null)
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (lastPlateableFruit.IsLoaded)
action();
else
- lastPlateableFruit.OnLoadComplete = _ => action();
+ lastPlateableFruit.OnLoadComplete += _ => action();
}
if (result.IsHit && fruit.CanBePlated)
@@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected bool Dashing
{
- get { return dashing; }
+ get => dashing;
set
{
if (value == dashing) return;
@@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
protected bool Trail
{
- get { return trail; }
+ get => trail;
set
{
if (value == trail) return;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
similarity index 88%
rename from osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
rename to osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index f421969449..406dc10eea 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -17,13 +17,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.UI
{
- public class CatchRulesetContainer : ScrollingRulesetContainer
+ public class DrawableCatchRuleset : DrawableScrollingRuleset
{
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant;
protected override bool UserScrollSpeedAdjustment => false;
- public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
Direction.Value = ScrollingDirection.Down;
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation);
- public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
+ protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
public override DrawableHitObject GetVisualRepresentation(CatchHitObject h)
{
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..9cd1e47023
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Mania.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..d47ac4643f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Rulesets.Mania.Tests.iOS
+{
+ public class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..8780204d5b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Rulesets.Mania.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Rulesets-Mania-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
new file mode 100644
index 0000000000..24abccb19d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
@@ -0,0 +1,45 @@
+
+
+
+
+ Debug
+ iPhoneSimulator
+ {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}
+ Exe
+ osu.Game.Rulesets.Mania.Tests
+ osu.Game.Rulesets.Mania.Tests.iOS
+
+
+
+
+
+
+ libbass.a
+ PreserveNewest
+
+
+ libbass_fx.a
+ PreserveNewest
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {48F4582B-7687-4621-9CBE-5C24197CB536}
+ osu.Game.Rulesets.Mania
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 7a2b4e7b39..6b95975059 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -40,29 +40,29 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
- public class ManiaConvertMapping : ConvertMapping, IEquatable
- {
- public uint RandomW;
- public uint RandomX;
- public uint RandomY;
- public uint RandomZ;
+ public class ManiaConvertMapping : ConvertMapping, IEquatable
+ {
+ public uint RandomW;
+ public uint RandomX;
+ public uint RandomY;
+ public uint RandomZ;
- public ManiaConvertMapping()
- {
- }
+ public ManiaConvertMapping()
+ {
+ }
- public ManiaConvertMapping(IBeatmapConverter converter)
- {
- var maniaConverter = (ManiaBeatmapConverter)converter;
- RandomW = maniaConverter.Random.W;
- RandomX = maniaConverter.Random.X;
- RandomY = maniaConverter.Random.Y;
- RandomZ = maniaConverter.Random.Z;
- }
+ public ManiaConvertMapping(IBeatmapConverter converter)
+ {
+ var maniaConverter = (ManiaBeatmapConverter)converter;
+ RandomW = maniaConverter.Random.W;
+ RandomX = maniaConverter.Random.X;
+ RandomY = maniaConverter.Random.Y;
+ RandomZ = maniaConverter.Random.Z;
+ }
- public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
- public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
- }
+ public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
+ public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
+ }
public struct ConvertValue : IEquatable
{
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
new file mode 100644
index 0000000000..2c36e81190
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mania.Difficulty;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
+
+ [TestCase(2.3683365342338796d, "diffcalc-test")]
+ public void Test(double expected, string name)
+ => base.Test(expected, name);
+
+ protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
+
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs
index 32f455bb73..e721eb6fd9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs
@@ -3,7 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
@@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
{
- var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
- config.BindWith(ManiaSetting.ScrollDirection, direction);
+ var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
index 2f4b51e372..0bc2c3ea28 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
@@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- TextSize = 14,
+ Font = OsuFont.GetFont(size: 14),
Text = description
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index e26d2433f9..fd17285a38 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 380ce533bb..71df68c087 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
public int TargetColumns;
+ public bool Dual;
public readonly bool IsForCurrentRuleset;
// Internal for testing purposes
@@ -45,7 +46,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
if (IsForCurrentRuleset)
+ {
TargetColumns = (int)Math.Max(1, roundedCircleSize);
+ if (TargetColumns >= 10)
+ {
+ TargetColumns = TargetColumns / 2;
+ Dual = true;
+ }
+ }
else
{
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
@@ -70,14 +78,22 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return base.ConvertBeatmap(original);
}
- protected override Beatmap CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
+ protected override Beatmap CreateBeatmap()
+ {
+ beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
+
+ if (Dual)
+ beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
+
+ return beatmap;
+ }
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
- var maniaOriginal = original as ManiaHitObject;
- if (maniaOriginal != null)
+ if (original is ManiaHitObject maniaOriginal)
{
yield return maniaOriginal;
+
yield break;
}
@@ -92,6 +108,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private readonly List prevNoteTimes = new List(max_notes_for_density);
private double density = int.MaxValue;
+
private void computeDensity(double newNoteTime)
{
if (prevNoteTimes.Count == max_notes_for_density)
@@ -104,6 +121,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime;
private Vector2 lastPosition;
private PatternType lastStair = PatternType.Stair;
+
private void recordNote(double time, Vector2 position)
{
lastTime = time;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 3eff2d62f3..ed52bdd23f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (originalPattern.HitObjects.Count() == 1)
{
yield return originalPattern;
+
yield break;
}
@@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
+
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
}
@@ -142,6 +144,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
+
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
}
@@ -149,11 +152,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
+
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
}
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
+
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 7004ea4969..34f5f5c415 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
- // If we convert to 7K + 1, let's not overload the special key
- && (TotalColumns != 8 || lastColumn != 0)
- // Make sure the last column was not the centre column
- && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
+ // If we convert to 7K + 1, let's not overload the special key
+ && (TotalColumns != 8 || lastColumn != 0)
+ // Make sure the last column was not the centre column
+ && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
int column = RandomStart + TotalColumns - lastColumn - 1;
@@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
+
return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
}
@@ -179,6 +180,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
+
return pattern = generateRandomPattern(1, 0.62, 0, 0);
}
@@ -186,6 +188,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
+
return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
}
@@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.18, 0, 0, 0);
+
return pattern = generateRandomPattern(0.45, 0, 0, 0);
}
@@ -250,6 +254,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
else
last = GetRandomColumn();
+
return last;
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index cf5dc4fe66..b702291c5d 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return 4;
if (val >= 1 - p3)
return 3;
+
return val >= 1 - p2 ? 2 : 1;
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
index a6fa234960..a3cd455886 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
@@ -12,51 +12,63 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
internal enum PatternType
{
None = 0,
+
///
/// Keep the same as last row.
///
ForceStack = 1 << 0,
+
///
/// Keep different from last row.
///
ForceNotStack = 1 << 1,
+
///
/// Keep as single note at its original position.
///
KeepSingle = 1 << 2,
+
///
/// Use a lower random value.
///
LowProbability = 1 << 3,
+
///
/// Reserved.
///
Alternate = 1 << 4,
+
///
/// Ignore the repeat count.
///
ForceSigSlider = 1 << 5,
+
///
/// Convert slider to circle.
///
ForceNotSlider = 1 << 6,
+
///
/// Notes gathered together.
///
Gathered = 1 << 7,
Mirror = 1 << 8,
+
///
/// Change 0 -> 6.
///
Reverse = 1 << 9,
+
///
/// 1 -> 5 -> 1 -> 5 like reverse.
///
Cycle = 1 << 10,
+
///
/// Next note will be at column + 1.
///
Stair = 1 << 11,
+
///
/// Next note will be at column - 1.
///
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
similarity index 57%
rename from osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
rename to osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index 4e0ad31105..b591f9da22 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration
{
- public class ManiaConfigManager : RulesetConfigManager
+ public class ManiaRulesetConfigManager : RulesetConfigManager
{
- public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
+ public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
@@ -19,17 +19,17 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
- Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
+ Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
- new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
+ new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
};
}
- public enum ManiaSetting
+ public enum ManiaRulesetSetting
{
ScrollTime,
ScrollDirection
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index 2f614ea14b..3ff665d2c8 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -2,17 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
public double GreatHitWindow;
-
- public ManiaDifficultyAttributes(Mod[] mods, double starRating)
- : base(mods, starRating)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 5b6fd4ecf3..59fed1031f 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -1,34 +1,24 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty
{
- internal class ManiaDifficultyCalculator : DifficultyCalculator
+ public class ManiaDifficultyCalculator : DifficultyCalculator
{
private const double star_scaling_factor = 0.018;
- ///
- /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step.
- /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
- /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
- ///
- private const double strain_step = 400;
-
- ///
- /// The weighting of each strain value decays to this number * it's previous value
- ///
- private const double decay_weight = 0.9;
-
private readonly bool isForCurrentRuleset;
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
@@ -37,108 +27,70 @@ namespace osu.Game.Rulesets.Mania.Difficulty
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
- if (!beatmap.HitObjects.Any())
- return new ManiaDifficultyAttributes(mods, 0);
+ if (beatmap.HitObjects.Count == 0)
+ return new ManiaDifficultyAttributes { Mods = mods };
- var difficultyHitObjects = new List();
-
- int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
-
- // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
- // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
- difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
-
- if (!calculateStrainValues(difficultyHitObjects, timeRate))
- return new DifficultyAttributes(mods, 0);
-
- double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
-
- return new ManiaDifficultyAttributes(mods, starRating)
+ return new ManiaDifficultyAttributes
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
+ StarRating = difficultyValue(skills) * star_scaling_factor,
+ Mods = mods,
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
};
}
- private bool calculateStrainValues(List objects, double timeRate)
+ private double difficultyValue(Skill[] skills)
{
- // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
- using (var hitObjectsEnumerator = objects.GetEnumerator())
+ // Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section
+ var overall = skills.OfType().Single();
+ var aggregatePeaks = new List(Enumerable.Repeat(0.0, overall.StrainPeaks.Count));
+
+ foreach (var individual in skills.OfType())
{
- if (!hitObjectsEnumerator.MoveNext())
- return false;
-
- ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current;
-
- // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
- while (hitObjectsEnumerator.MoveNext())
+ for (int i = 0; i < individual.StrainPeaks.Count; i++)
{
- var next = hitObjectsEnumerator.Current;
- next?.CalculateStrains(current, timeRate);
- current = next;
+ double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i];
+
+ if (aggregate > aggregatePeaks[i])
+ aggregatePeaks[i] = aggregate;
}
-
- return true;
- }
- }
-
- private double calculateDifficulty(List objects, double timeRate)
- {
- double actualStrainStep = strain_step * timeRate;
-
- // Find the highest strain value within each strain step
- List highestStrains = new List();
- double intervalEndTime = actualStrainStep;
- double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
-
- ManiaHitObjectDifficulty previousHitObject = null;
- foreach (var hitObject in objects)
- {
- // While we are beyond the current interval push the currently available maximum to our strain list
- while (hitObject.BaseHitObject.StartTime > intervalEndTime)
- {
- highestStrains.Add(maximumStrain);
-
- // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
- // until the beginning of the next interval.
- if (previousHitObject == null)
- {
- maximumStrain = 0;
- }
- else
- {
- double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
- double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
- maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay;
- }
-
- // Go to the next time interval
- intervalEndTime += actualStrainStep;
- }
-
- // Obtain maximum strain
- double strain = hitObject.IndividualStrain + hitObject.OverallStrain;
- maximumStrain = Math.Max(strain, maximumStrain);
-
- previousHitObject = hitObject;
}
- // Build the weighted sum over the highest strains for each interval
+ aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
+
double difficulty = 0;
double weight = 1;
- highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
- foreach (double strain in highestStrains)
+ // Difficulty is the weighted sum of the highest strains from every section.
+ foreach (double strain in aggregatePeaks)
{
- difficulty += weight * strain;
- weight *= decay_weight;
+ difficulty += strain * weight;
+ weight *= 0.9;
}
return difficulty;
}
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
+ {
+ for (int i = 1; i < beatmap.HitObjects.Count; i++)
+ yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
+ }
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap)
+ {
+ int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
+
+ var skills = new List { new Overall(columnCount) };
+
+ for (int i = 0; i < columnCount; i++)
+ skills.Add(new Individual(i, columnCount));
+
+ return skills.ToArray();
+ }
+
protected override Mod[] DifficultyAdjustmentMods
{
get
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 2f4870f647..b99bddee96 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
- * strainValue
- * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
+ * strainValue
+ * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs
new file mode 100644
index 0000000000..29ba934e9f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing
+{
+ public class ManiaDifficultyHitObject : DifficultyHitObject
+ {
+ public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject;
+
+ public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
+ : base(hitObject, lastObject, clockRate)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
new file mode 100644
index 0000000000..059cd39641
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
@@ -0,0 +1,47 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mania.Objects;
+
+namespace osu.Game.Rulesets.Mania.Difficulty.Skills
+{
+ public class Individual : Skill
+ {
+ protected override double SkillMultiplier => 1;
+ protected override double StrainDecayBase => 0.125;
+
+ private readonly double[] holdEndTimes;
+
+ private readonly int column;
+
+ public Individual(int column, int columnCount)
+ {
+ this.column = column;
+
+ holdEndTimes = new double[columnCount];
+ }
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ var maniaCurrent = (ManiaDifficultyHitObject)current;
+ var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+
+ try
+ {
+ if (maniaCurrent.BaseObject.Column != column)
+ return 0;
+
+ // We give a slight bonus if something is held meanwhile
+ return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2;
+ }
+ finally
+ {
+ holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
new file mode 100644
index 0000000000..ed25173d38
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mania.Objects;
+
+namespace osu.Game.Rulesets.Mania.Difficulty.Skills
+{
+ public class Overall : Skill
+ {
+ protected override double SkillMultiplier => 1;
+ protected override double StrainDecayBase => 0.3;
+
+ private readonly double[] holdEndTimes;
+
+ private readonly int columnCount;
+
+ public Overall(int columnCount)
+ {
+ this.columnCount = columnCount;
+
+ holdEndTimes = new double[columnCount];
+ }
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ var maniaCurrent = (ManiaDifficultyHitObject)current;
+ var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+
+ double holdFactor = 1.0; // Factor in case something else is held
+ double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
+
+ for (int i = 0; i < columnCount; i++)
+ {
+ // If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
+ if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i])
+ holdAddition = 1.0;
+
+ // ... this addition only is valid if there is _no_ other note with the same ending.
+ // Releasing multiple notes at the same time is just as easy as releasing one
+ if (endTime == holdEndTimes[i])
+ holdAddition = 0;
+
+ // We give a slight bonus if something is held meanwhile
+ if (holdEndTimes[i] > endTime)
+ holdFactor = 1.25;
+ }
+
+ holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
+
+ return (1 + holdAddition) * holdFactor;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 9bf14b0672..d64c5dbc6a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Graphics;
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
similarity index 83%
rename from osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs
rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index 89e531fd9f..acafaffee6 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -10,11 +10,11 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Edit
{
- public class ManiaEditRulesetContainer : ManiaRulesetContainer
+ public class DrawableManiaEditRuleset : DrawableManiaRuleset
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 3dbbd132a6..56c9471462 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit
[Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
{
- protected new ManiaEditRulesetContainer RulesetContainer { get; private set; }
+ protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; }
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
@@ -32,23 +32,23 @@ namespace osu.Game.Rulesets.Mania.Edit
///
/// The screen-space position.
/// The column which intersects with .
- public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition);
+ public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition);
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns;
+ public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns;
- protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
{
- RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap);
+ DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
- dependencies.CacheAs(RulesetContainer.ScrollingInfo);
+ dependencies.CacheAs(DrawableRuleset.ScrollingInfo);
- return RulesetContainer;
+ return DrawableRuleset;
}
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
index 864084b407..292990fd7e 100644
--- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs
+++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
@@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mania
{
[Description("Special 1")]
Special1 = 1,
+
[Description("Special 2")]
Special2,
@@ -26,38 +27,55 @@ namespace osu.Game.Rulesets.Mania
// above at a later time, without breaking replays/configs.
[Description("Key 1")]
Key1 = 10,
+
[Description("Key 2")]
Key2,
+
[Description("Key 3")]
Key3,
+
[Description("Key 4")]
Key4,
+
[Description("Key 5")]
Key5,
+
[Description("Key 6")]
Key6,
+
[Description("Key 7")]
Key7,
+
[Description("Key 8")]
Key8,
+
[Description("Key 9")]
Key9,
+
[Description("Key 10")]
Key10,
+
[Description("Key 11")]
Key11,
+
[Description("Key 12")]
Key12,
+
[Description("Key 13")]
Key13,
+
[Description("Key 14")]
Key14,
+
[Description("Key 15")]
Key15,
+
[Description("Key 16")]
Key16,
+
[Description("Key 17")]
Key17,
+
[Description("Key 18")]
Key18,
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 57728dd134..a4a10f1742 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -12,6 +12,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
@@ -145,6 +146,11 @@ namespace osu.Game.Rulesets.Mania
{
new MultiMod(new ManiaModAutoplay(), new ModCinema()),
};
+ case ModType.Fun:
+ return new Mod[]
+ {
+ new MultiMod(new ModWindUp(), new ModWindDown())
+ };
default:
return new Mod[] { };
}
@@ -162,7 +168,7 @@ namespace osu.Game.Rulesets.Mania
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
- public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
+ public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
@@ -330,12 +336,12 @@ namespace osu.Game.Rulesets.Mania
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
- for (int i = 0; i < columns / 2; i++)
- bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
-
if (columns % 2 == 1)
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
+ for (int i = 0; i < columns / 2; i++)
+ bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
+
nextNormalAction = currentNormalAction;
return bindings;
}
@@ -349,6 +355,7 @@ namespace osu.Game.Rulesets.Mania
/// Number of columns in this stage lies at (item - Single).
///
Single = 0,
+
///
/// Columns are grouped into two stages.
/// Overall number of columns lies at (item - Dual), further computation is required for
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
index 2ab40b2bc6..2ebfd0cfc1 100644
--- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Mania
[BackgroundDependencyLoader]
private void load()
{
- var config = (ManiaConfigManager)Config;
+ var config = (ManiaRulesetConfigManager)Config;
Children = new Drawable[]
{
new SettingsEnumDropdown
{
LabelText = "Scrolling direction",
- Bindable = config.GetBindable(ManiaSetting.ScrollDirection)
+ Bindable = config.GetBindable(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider
{
LabelText = "Scroll speed",
- Bindable = config.GetBindable(ManiaSetting.ScrollTime)
+ Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime)
},
};
}
diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
index a88512e12f..a9cd7f2476 100644
--- a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
@@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = X ^ X << 11;
+ uint t = X ^ (X << 11);
X = Y;
Y = Z;
Z = W;
- return W = W ^ W >> 19 ^ t ^ t >> 8;
+ return W = W ^ (W >> 19) ^ t ^ (t >> 8);
}
///
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
index 02eb7ac883..c05e979e9a 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModAutoplay : ModAutoplay
{
- protected override Score CreateReplayScore(Beatmap beatmap) => new Score
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index 7b2ee9a632..c78bf72979 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap
+ public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter
{
public override string Name => "Dual Stages";
public override string Acronym => "DS";
@@ -29,24 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
if (isForCurrentRuleset)
return;
- mbc.TargetColumns *= 2;
- }
-
- public void ApplyToBeatmap(Beatmap beatmap)
- {
- if (isForCurrentRuleset)
- return;
-
- var maniaBeatmap = (ManiaBeatmap)beatmap;
-
- var newDefinitions = new List();
- foreach (var existing in maniaBeatmap.Stages)
- {
- newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
- newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
- }
-
- maniaBeatmap.Stages = newDefinitions;
+ mbc.Dual = true;
}
public PlayfieldType PlayfieldType => PlayfieldType.Dual;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index baa757008f..6893e1e73b 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects;
@@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods
}
}
- protected override void OnComboChange(int newCombo)
+ protected override void OnComboChange(ValueChangedEvent e)
{
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 8b2da60a9e..4bfd940aa0 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@@ -75,16 +76,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(Tail);
}
- protected override void OnDirectionChanged(ScrollingDirection direction)
+ protected override void OnDirectionChanged(ValueChangedEvent e)
{
- base.OnDirectionChanged(direction);
+ base.OnDirectionChanged(e);
- bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
index eb7d153a90..43aac7907f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index bc34648dd8..a78524011f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -3,7 +3,7 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive;
- protected virtual void OnDirectionChanged(ScrollingDirection direction)
+ protected virtual void OnDirectionChanged(ValueChangedEvent e)
{
- Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 7ed8e89f95..7ef90cdb9c 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osuTK.Graphics;
using osu.Framework.Graphics;
@@ -31,16 +32,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
InternalChild = headPiece = new NotePiece();
}
- protected override void OnDirectionChanged(ScrollingDirection direction)
+ protected override void OnDirectionChanged(ValueChangedEvent e)
{
- base.OnDirectionChanged(direction);
+ base.OnDirectionChanged(e);
- headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ headPiece.Anchor = headPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
index 3decd46888..2baf1ad520 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
@@ -77,11 +77,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
if (accentColour == value)
return;
+
accentColour = value;
updateAccentColour();
@@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public bool Hitting
{
- get { return hitting; }
+ get => hitting;
set
{
hitting = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
index 4a54ac0aac..b146a33fd3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
@@ -35,13 +35,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
private Color4 accentColour;
+
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
if (accentColour == value)
return;
+
accentColour = value;
updateGlow();
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
index 8927e0b068..9e0307c5c2 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
@@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public Color4 AccentColour
{
- get { return Colour; }
- set { Colour = value; }
+ get => Colour;
+ set => Colour = value;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index 65a376a3a8..bb33693783 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -49,20 +49,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(direction =>
+ direction.BindValueChanged(dir =>
{
- colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}, true);
}
private Color4 accentColour;
+
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
if (accentColour == value)
return;
+
accentColour = value;
colouredBox.Colour = AccentColour.Lighten(0.9f);
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 8bb22fb586..5e9f46d9c7 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -17,9 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects
public double EndTime => StartTime + Duration;
private double duration;
+
public double Duration
{
- get { return duration; }
+ get => duration;
set
{
duration = value;
@@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override double StartTime
{
- get { return base.StartTime; }
+ get => base.StartTime;
set
{
base.StartTime = value;
@@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override int Column
{
- get { return base.Column; }
+ get => base.Column;
set
{
base.Column = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 5765817b63..70720a926b 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public virtual int Column
{
- get => ColumnBindable;
+ get => ColumnBindable.Value;
set => ColumnBindable.Value = value;
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs
deleted file mode 100644
index b6ea8c8665..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Rulesets.Objects.Types;
-using System;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- internal class ManiaHitObjectDifficulty
- {
- ///
- /// Factor by how much individual / overall strain decays per second.
- ///
- ///
- /// These values are results of tweaking a lot and taking into account general feedback.
- ///
- internal const double INDIVIDUAL_DECAY_BASE = 0.125;
- internal const double OVERALL_DECAY_BASE = 0.30;
-
- internal ManiaHitObject BaseHitObject;
-
- private readonly int beatmapColumnCount;
-
- private readonly double endTime;
- private readonly double[] heldUntil;
-
- ///
- /// Measures jacks or more generally: repeated presses of the same button
- ///
- private readonly double[] individualStrains;
-
- internal double IndividualStrain
- {
- get
- {
- return individualStrains[BaseHitObject.Column];
- }
-
- set
- {
- individualStrains[BaseHitObject.Column] = value;
- }
- }
-
- ///
- /// Measures note density in a way
- ///
- internal double OverallStrain = 1;
-
- public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount)
- {
- BaseHitObject = baseHitObject;
-
- endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime;
-
- beatmapColumnCount = columnCount;
- heldUntil = new double[beatmapColumnCount];
- individualStrains = new double[beatmapColumnCount];
-
- for (int i = 0; i < beatmapColumnCount; ++i)
- {
- individualStrains[i] = 0;
- heldUntil[i] = 0;
- }
- }
-
- internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate)
- {
- // TODO: Factor in holds
- double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
- double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000);
- double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000);
-
- double holdFactor = 1.0; // Factor to all additional strains in case something else is held
- double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
-
- // Fill up the heldUntil array
- for (int i = 0; i < beatmapColumnCount; ++i)
- {
- heldUntil[i] = previousHitObject.heldUntil[i];
-
- // If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
- if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i])
- {
- holdAddition = 1.0;
- }
-
- // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
- if (endTime == heldUntil[i])
- {
- holdAddition = 0;
- }
-
- // We give a slight bonus to everything if something is held meanwhile
- if (heldUntil[i] > endTime)
- {
- holdFactor = 1.25;
- }
-
- // Decay individual strains
- individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay;
- }
-
- heldUntil[BaseHitObject.Column] = endTime;
-
- // Increase individual strain in own column
- IndividualStrain += 2.0 * holdFactor;
-
- OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
index 9ebe638c30..5f2ceab48b 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
private static readonly IReadOnlyDictionary base_ranges = new Dictionary
{
{ HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68 ) },
+ { HitResult.Great, (128, 98, 68) },
{ HitResult.Good, (194, 164, 134) },
{ HitResult.Ok, (254, 224, 194) },
{ HitResult.Meh, (302, 272, 242) },
diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
index 21e6cf322b..f3ea6c7b71 100644
--- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
@@ -9,3 +9,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.iOS")]
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index df6afa040e..65b7d54cd2 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -5,13 +5,12 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
{
- internal class ManiaAutoGenerator : AutoGenerator
+ internal class ManiaAutoGenerator : AutoGenerator
{
public const double RELEASE_DELAY = 20;
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu
new file mode 100644
index 0000000000..4c877c6193
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu
@@ -0,0 +1,180 @@
+osu file format v14
+
+[General]
+Mode: 3
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+37500,-50,4,2,1,50,0,0
+41500,-25,4,2,1,50,0,0
+
+[HitObjects]
+// jacks spaced 1/1 beat apart
+64,192,0,1,0,0:0:0:0:
+64,192,500,1,0,0:0:0:0:
+64,192,1000,1,0,0:0:0:0:
+64,192,1500,1,0,0:0:0:0:
+64,192,2000,1,0,0:0:0:0:
+64,192,2500,1,0,0:0:0:0:
+
+// jacks spaced 1/2 beat apart
+64,192,3500,1,0,0:0:0:0:
+64,192,3750,1,0,0:0:0:0:
+64,192,4000,1,0,0:0:0:0:
+64,192,4250,1,0,0:0:0:0:
+64,192,4500,1,0,0:0:0:0:
+64,192,4750,1,0,0:0:0:0:
+64,192,5000,1,0,0:0:0:0:
+64,192,6000,1,0,0:0:0:0:
+
+// doubles jacks spaced 1/2 beat apart
+192,192,6000,1,0,0:0:0:0:
+64,192,6250,1,0,0:0:0:0:
+192,192,6250,1,0,0:0:0:0:
+64,192,6500,1,0,0:0:0:0:
+192,192,6500,1,0,0:0:0:0:
+64,192,6750,1,0,0:0:0:0:
+192,192,6750,1,0,0:0:0:0:
+64,192,7000,1,0,0:0:0:0:
+192,192,7000,1,0,0:0:0:0:
+64,192,7250,1,0,0:0:0:0:
+192,192,7250,1,0,0:0:0:0:
+64,192,7500,1,0,0:0:0:0:
+192,192,7500,1,0,0:0:0:0:
+
+// trill spaced 1/2 beat apart
+64,192,8500,1,0,0:0:0:0:
+192,192,8750,1,0,0:0:0:0:
+64,192,9000,1,0,0:0:0:0:
+192,192,9250,1,0,0:0:0:0:
+64,192,9500,1,0,0:0:0:0:
+192,192,9750,1,0,0:0:0:0:
+64,192,10000,1,0,0:0:0:0:
+192,192,10250,1,0,0:0:0:0:
+64,192,10500,1,0,0:0:0:0:
+
+// stair spaced 1/4 apart
+64,192,11500,1,0,0:0:0:0:
+192,192,11625,1,0,0:0:0:0:
+320,192,11750,1,0,0:0:0:0:
+448,192,11875,1,0,0:0:0:0:
+320,192,12000,1,0,0:0:0:0:
+192,192,12125,1,0,0:0:0:0:
+64,192,12250,1,0,0:0:0:0:
+192,192,12375,1,0,0:0:0:0:
+320,192,12500,1,0,0:0:0:0:
+448,192,12625,1,0,0:0:0:0:
+
+// jumpstreams?
+64,192,13500,1,0,0:0:0:0:
+192,192,13625,1,0,0:0:0:0:
+320,192,13750,1,0,0:0:0:0:
+448,192,13875,1,0,0:0:0:0:
+320,192,14000,1,0,0:0:0:0:
+192,192,14000,1,0,0:0:0:0:
+64,192,14125,1,0,0:0:0:0:
+192,192,14250,1,0,0:0:0:0:
+320,192,14250,1,0,0:0:0:0:
+448,192,14250,1,0,0:0:0:0:
+64,192,14375,1,0,0:0:0:0:
+64,192,14500,1,0,0:0:0:0:
+320,192,14625,1,0,0:0:0:0:
+448,192,14625,1,0,0:0:0:0:
+192,192,14625,1,0,0:0:0:0:
+192,192,14750,1,0,0:0:0:0:
+64,192,14875,1,0,0:0:0:0:
+192,192,15000,1,0,0:0:0:0:
+320,192,15125,1,0,0:0:0:0:
+448,192,15125,1,0,0:0:0:0:
+
+// double... jumps?
+64,192,16000,1,0,0:0:0:0:
+64,192,16250,1,0,0:0:0:0:
+192,192,16250,1,0,0:0:0:0:
+192,192,16500,1,0,0:0:0:0:
+320,192,16500,1,0,0:0:0:0:
+320,192,16750,1,0,0:0:0:0:
+448,192,16750,1,0,0:0:0:0:
+448,192,17000,1,0,0:0:0:0:
+
+// notes alongside hold
+64,192,18000,128,0,18500:0:0:0:0:
+192,192,18000,1,0,0:0:0:0:
+192,192,18250,1,0,0:0:0:0:
+192,192,18500,1,0,0:0:0:0:
+
+// notes overlapping hold
+64,192,19500,1,0,0:0:0:0:
+192,192,19625,128,0,20875:0:0:0:0:
+64,192,19750,1,0,0:0:0:0:
+64,192,20000,1,0,0:0:0:0:
+64,192,20250,1,0,0:0:0:0:
+64,192,20500,1,0,0:0:0:0:
+64,192,20750,1,0,0:0:0:0:
+64,192,21000,1,0,0:0:0:0:
+
+// simultaneous holds
+64,192,22000,128,0,23000:0:0:0:0:
+192,192,22000,128,0,23000:0:0:0:0:
+320,192,22000,128,0,23000:0:0:0:0:
+448,192,22000,128,0,23000:0:0:0:0:
+
+// hold stairs
+64,192,24500,128,0,25500:0:0:0:0:
+192,192,24625,128,0,25375:0:0:0:0:
+320,192,24750,128,0,25250:0:0:0:0:
+448,192,24875,128,0,25125:0:0:0:0:
+448,192,25375,128,0,26375:0:0:0:0:
+320,192,25500,128,0,26250:0:0:0:0:
+192,192,25625,128,0,26125:0:0:0:0:
+64,192,25750,128,0,26000:0:0:0:0:
+
+// quads
+64,192,26500,1,0,0:0:0:0:
+64,192,27500,1,0,0:0:0:0:
+192,192,27500,1,0,0:0:0:0:
+320,192,27500,1,0,0:0:0:0:
+448,192,27500,1,0,0:0:0:0:
+64,192,27750,1,0,0:0:0:0:
+192,192,27750,1,0,0:0:0:0:
+320,192,27750,1,0,0:0:0:0:
+448,192,27750,1,0,0:0:0:0:
+64,192,28000,1,0,0:0:0:0:
+192,192,28000,1,0,0:0:0:0:
+320,192,28000,1,0,0:0:0:0:
+448,192,28000,1,0,0:0:0:0:
+64,192,28250,1,0,0:0:0:0:
+192,192,28250,1,0,0:0:0:0:
+320,192,28250,1,0,0:0:0:0:
+448,192,28250,1,0,0:0:0:0:
+64,192,28500,1,0,0:0:0:0:
+192,192,28500,1,0,0:0:0:0:
+320,192,28500,1,0,0:0:0:0:
+448,192,28500,1,0,0:0:0:0:
+
+// double-trills
+64,192,29500,1,0,0:0:0:0:
+192,192,29500,1,0,0:0:0:0:
+320,192,29625,1,0,0:0:0:0:
+448,192,29625,1,0,0:0:0:0:
+64,192,29750,1,0,0:0:0:0:
+192,192,29750,1,0,0:0:0:0:
+320,192,29875,1,0,0:0:0:0:
+448,192,29875,1,0,0:0:0:0:
+64,192,30000,1,0,0:0:0:0:
+192,192,30000,1,0,0:0:0:0:
+320,192,30125,1,0,0:0:0:0:
+448,192,30125,1,0,0:0:0:0:
+64,192,30250,1,0,0:0:0:0:
+192,192,30250,1,0,0:0:0:0:
+320,192,30375,1,0,0:0:0:0:
+448,192,30375,1,0,0:0:0:0:
+64,192,30500,1,0,0:0:0:0:
+192,192,30500,1,0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index b94b64fa36..5c914d8eac 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
}
- public ManiaScoreProcessor(RulesetContainer rulesetContainer)
- : base(rulesetContainer)
+ public ManiaScoreProcessor(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
{
}
@@ -159,6 +159,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
}
}
- protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
+ public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 3e7884af6c..c59bed4ea7 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components;
@@ -82,28 +82,30 @@ namespace osu.Game.Rulesets.Mania.UI
TopLevelContainer.Add(explosionContainer.CreateProxy());
- Direction.BindValueChanged(d =>
+ Direction.BindValueChanged(dir =>
{
hitTargetContainer.Padding = new MarginPadding
{
- Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
- Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
+ Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
+ Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
- keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
}
public override Axes RelativeSizeAxes => Axes.Y;
private bool isSpecial;
+
public bool IsSpecial
{
- get { return isSpecial; }
+ get => isSpecial;
set
{
if (isSpecial == value)
return;
+
isSpecial = value;
Width = isSpecial ? special_column_width : column_width;
@@ -111,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.UI
}
private Color4 accentColour;
+
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
if (accentColour == value)
return;
+
accentColour = value;
background.AccentColour = value;
@@ -156,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
- if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements)
+ if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
explosionContainer.Add(new HitExplosion(judgedObject)
@@ -167,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.UI
public bool OnPressed(ManiaAction action)
{
- if (action != Action)
+ if (action != Action.Value)
return false;
var nextObject =
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index b7f291b5a2..b4e29ae9f9 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
};
direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(direction =>
+ direction.BindValueChanged(dir =>
{
- backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
updateColours();
}, true);
}
@@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
if (accentColour == value)
return;
+
accentColour = value;
updateColours();
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index 7f5687e600..89e8cd9b5a 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(direction =>
+ direction.BindValueChanged(dir =>
{
- Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
@@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
if (accentColour == value)
return;
+
accentColour = value;
updateColours();
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
index a180ffbd3b..03b55cbead 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
};
direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(direction =>
+ direction.BindValueChanged(dir =>
{
gradient.Colour = ColourInfo.GradientVertical(
- direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
- direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
+ dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
+ dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
}, true);
}
@@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
if (accentColour == value)
return;
+
accentColour = value;
updateColours();
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
index 1469924bb2..8797f014df 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
@@ -19,25 +19,18 @@ namespace osu.Game.Rulesets.Mania.UI
private void load()
{
if (JudgementText != null)
- JudgementText.TextSize = 25;
+ JudgementText.Font = JudgementText.Font.With(size: 25);
}
- protected override void LoadComplete()
+ protected override double FadeInDuration => 50;
+
+ protected override void ApplyHitAnimations()
{
- base.LoadComplete();
+ JudgementBody.ScaleTo(0.8f);
+ JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
- this.FadeInFromZero(50, Easing.OutQuint);
-
- if (Result.IsHit)
- {
- JudgementBody.ScaleTo(0.8f);
- JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
-
- JudgementBody.Delay(50).ScaleTo(0.75f, 250);
- this.Delay(50).FadeOut(200);
- }
-
- Expire();
+ JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250);
+ this.Delay(FadeInDuration).FadeOut(200);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
similarity index 80%
rename from osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
rename to osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index 892ad584dc..a019401d5b 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
@@ -15,7 +15,6 @@ using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
-using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
@@ -29,17 +28,19 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
- public class ManiaRulesetContainer : ScrollingRulesetContainer
+ public class DrawableManiaRuleset : DrawableScrollingRuleset
{
+ protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield;
+
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
public IEnumerable BarLines;
- protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
+ protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable configDirection = new Bindable();
- public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
// Generate the bar lines
@@ -75,10 +76,10 @@ namespace osu.Game.Rulesets.Mania.UI
{
BarLines.ForEach(Playfield.Add);
- Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
- configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
+ Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
+ configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
- Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
+ Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
}
///
@@ -96,9 +97,9 @@ namespace osu.Game.Rulesets.Mania.UI
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
- public override int Variant => (int)(Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns;
+ public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
- public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
+ protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h)
{
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index bce333ff34..a28de7ea58 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -136,12 +136,12 @@ namespace osu.Game.Rulesets.Mania.UI
AddColumn(column);
}
- Direction.BindValueChanged(d =>
+ Direction.BindValueChanged(dir =>
{
barLineContainer.Padding = new MarginPadding
{
- Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
- Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
+ Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
+ Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
};
}, true);
}
@@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
- if (!judgedObject.DisplayResult || !DisplayJudgements)
+ if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
judgements.Clear();
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..01e635f09c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Osu.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..7a0797a909
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Rulesets.Osu.Tests.iOS
+{
+ public class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..f79215cf54
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Rulesets.Osu.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Rulesets-Osu-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
new file mode 100644
index 0000000000..9930a166e3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
@@ -0,0 +1,45 @@
+
+
+
+
+ Debug
+ iPhoneSimulator
+ {6653CA6F-DB06-4604-A3FD-762E25C2AF96}
+ Exe
+ osu.Game.Rulesets.Osu.Tests
+ osu.Game.Rulesets.Osu.Tests.iOS
+
+
+
+
+
+
+ libbass.a
+ PreserveNewest
+
+
+ libbass_fx.a
+ PreserveNewest
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}
+ osu.Game.Rulesets.Osu
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 0ebcad3637..f7d1ff4db1 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -33,9 +33,11 @@ namespace osu.Game.Rulesets.Osu.Tests
case Slider slider:
foreach (var nested in slider.NestedHitObjects)
yield return createConvertValue(nested);
+
break;
default:
yield return createConvertValue(hitObject);
+
break;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
new file mode 100644
index 0000000000..e55dc1f902
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Osu.Difficulty;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
+
+ [TestCase(6.931145117263422, "diffcalc-test")]
+ public void Test(double expected, string name)
+ => base.Test(expected, name);
+
+ protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
+
+ protected override Ruleset CreateRuleset() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs
index 6ebfe4fad1..5c1e775c01 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs
@@ -16,18 +16,18 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture]
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
{
- private GameplayCursor cursor;
+ private GameplayCursorContainer cursorContainer;
- public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) };
+ public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) };
- public CursorContainer Cursor => cursor;
+ public CursorContainer Cursor => cursorContainer;
public bool ProvidingUserCursor => true;
[BackgroundDependencyLoader]
private void load()
{
- Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both });
+ Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both });
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
index 5484f15581..e1e854e8dc 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
@@ -89,7 +89,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private readonly bool auto;
- public TestDrawableHitCircle(HitCircle h, bool auto) : base(h)
+ public TestDrawableHitCircle(HitCircle h, bool auto)
+ : base(h)
{
this.auto = auto;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
new file mode 100644
index 0000000000..8d097ff1c1
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestCaseHitCircleLongCombo : PlayerTestCase
+ {
+ public TestCaseHitCircleLongCombo()
+ : base(new OsuRuleset())
+ {
+ }
+
+ protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Ruleset = ruleset.RulesetInfo
+ }
+ };
+
+ for (int i = 0; i < 512; i++)
+ beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
+
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs
new file mode 100644
index 0000000000..720c3c66fe
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestCaseOsuPlayer : PlayerTestCase
+ {
+ public TestCaseOsuPlayer()
+ : base(new OsuRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
index d6b88c1c5f..35e8f3e17e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
@@ -16,6 +16,7 @@ using osuTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -300,6 +301,7 @@ namespace osu.Game.Rulesets.Osu.Tests
}
private float judgementOffsetDirection = 1;
+
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
var osuObject = judgedObject as DrawableOsuHitObject;
@@ -313,7 +315,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Origin = Anchor.Centre,
Text = result.IsHit ? "Hit!" : "Miss!",
Colour = result.IsHit ? Color4.Green : Color4.Red,
- TextSize = 30,
+ Font = OsuFont.GetFont(size: 30),
Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
});
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
new file mode 100644
index 0000000000..2f33982d41
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
@@ -0,0 +1,372 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestCaseSliderInput : RateAdjustedBeatmapTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SliderBall),
+ typeof(DrawableSlider),
+ typeof(DrawableSliderTick),
+ typeof(DrawableRepeatPoint),
+ typeof(DrawableOsuHitObject),
+ typeof(DrawableSliderHead),
+ typeof(DrawableSliderTail),
+ };
+
+ private const double time_before_slider = 250;
+ private const double time_slider_start = 1500;
+ private const double time_during_slide_1 = 2500;
+ private const double time_during_slide_2 = 3000;
+ private const double time_during_slide_3 = 3500;
+ private const double time_during_slide_4 = 3800;
+
+ private List judgementResults;
+ private bool allJudgedFired;
+
+ ///
+ /// Scenario:
+ /// - Press a key before a slider starts
+ /// - Press the other key on the slider head timed correctly while holding the original key
+ /// - Release the latter pressed key
+ /// Expected Result:
+ /// A passing test case will have the cursor lose tracking on replay frame 3.
+ ///
+ [Test]
+ public void TestInvalidKeyTransfer()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
+ });
+
+ AddAssert("Tracking lost", assertMidSliderJudgementFail);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key on the slider head timed correctly
+ /// - Press the other key in the middle of the slider while holding the original key
+ /// - Release the original key used to hit the slider
+ /// Expected Result:
+ /// A passing test case will have the cursor continue tracking on replay frame 3.
+ ///
+ [Test]
+ public void TestLeftBeforeSliderThenRightThenLettingGoOfLeft()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 },
+ });
+
+ AddAssert("Tracking retained", assertGreatJudge);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key on the slider head timed correctly
+ /// - Press the other key in the middle of the slider while holding the original key
+ /// - Release the new key that was pressed second
+ /// Expected Result:
+ /// A passing test case will have the cursor continue tracking on replay frame 3.
+ ///
+ [Test]
+ public void TestTrackingRetentionLeftRightLeft()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 },
+ });
+
+ AddAssert("Tracking retained", assertGreatJudge);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key before a slider starts
+ /// - Press the other key on the slider head timed correctly while holding the original key
+ /// - Release the key that was held down before the slider started.
+ /// Expected Result:
+ /// A passing test case will have the cursor continue tracking on replay frame 3
+ ///
+ [Test]
+ public void TestTrackingLeftBeforeSliderToRight()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 },
+ });
+
+ AddAssert("Tracking retained", assertGreatJudge);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key before a slider starts
+ /// - Hold the key down throughout the slider without pressing any other buttons.
+ /// Expected Result:
+ /// A passing test case will have the cursor track the slider, but miss the slider head.
+ ///
+ [Test]
+ public void TestTrackingPreclicked()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ });
+
+ AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key before a slider starts
+ /// - Hold the key down after the slider starts
+ /// - Move the cursor away from the slider body
+ /// - Move the cursor back onto the body
+ /// Expected Result:
+ /// A passing test case will have the cursor track the slider, miss the head, miss the ticks where its outside of the body, and resume tracking when the cursor returns.
+ ///
+ [Test]
+ public void TestTrackingReturnMidSlider()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
+ });
+
+ AddAssert("Tracking re-acquired", assertMidSliderJudgements);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key before a slider starts
+ /// - Press the other key on the slider head timed correctly while holding the original key
+ /// - Release the key used to hit the slider head
+ /// - While holding the first key, move the cursor away from the slider body
+ /// - Still holding the first key, move the cursor back to the slider body
+ /// Expected Result:
+ /// A passing test case will have the slider not track despite having the cursor return to the slider body.
+ ///
+ [Test]
+ public void TestTrackingReturnMidSliderKeyDownBefore()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
+ });
+
+ AddAssert("Tracking lost", assertMidSliderJudgementFail);
+ }
+
+ ///
+ /// Scenario:
+ /// - Wait for the slider to reach a mid-point
+ /// - Press a key away from the slider body
+ /// - While holding down the key, move into the slider body
+ /// Expected Result:
+ /// A passing test case will have the slider track the cursor after the cursor enters the slider body.
+ ///
+ [Test]
+ public void TestTrackingMidSlider()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
+ });
+
+ AddAssert("Tracking acquired", assertMidSliderJudgements);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key before the slider starts
+ /// - Press another key on the slider head while holding the original key
+ /// - Move out of the slider body while releasing the two pressed keys
+ /// - Move back into the slider body while pressing any key.
+ /// Expected Result:
+ /// A passing test case will have the slider track the cursor after the cursor enters the slider body.
+ ///
+ [Test]
+ public void TestMidSliderTrackingAcquired()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 },
+ });
+
+ AddAssert("Tracking acquired", assertMidSliderJudgements);
+ }
+
+ [Test]
+ public void TestMidSliderTrackingAcquiredWithMouseDownOutsideSlider()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 },
+ });
+
+ AddAssert("Tracking acquired", assertMidSliderJudgements);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key on the slider head
+ /// - While holding the key, move outside of the slider body with the cursor
+ /// - Release the key while outside of the slider body
+ /// - Press the key again while outside of the slider body
+ /// - Move back into the slider body while holding the pressed key
+ /// Expected Result:
+ /// A passing test case will have the slider track the cursor after the cursor enters the slider body.
+ ///
+ [Test]
+ public void TestTrackingReleasedValidKey()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
+ new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_2 },
+ new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 },
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
+ });
+
+ AddAssert("Tracking acquired", assertMidSliderJudgements);
+ }
+
+ private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
+
+ private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
+
+ private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
+
+ private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
+
+ private ScoreAccessibleReplayPlayer currentPlayer;
+
+ private void performTest(List frames)
+ {
+ // Empty frame to be added as a workaround for first frame behavior.
+ // If an input exists on the first frame, the input would apply to the entire intro lead-in
+ // Likely requires some discussion regarding how first frame inputs should be handled.
+ frames.Insert(0, new OsuReplayFrame());
+
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = new TestWorkingBeatmap(new Beatmap
+ {
+ HitObjects =
+ {
+ new Slider
+ {
+ StartTime = time_slider_start,
+ Position = new Vector2(0, 0),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(25, 0),
+ }, 25),
+ }
+ },
+ ControlPointInfo =
+ {
+ DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
+ },
+ BeatmapInfo =
+ {
+ BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
+ Ruleset = new OsuRuleset().RulesetInfo
+ },
+ }, Clock);
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } })
+ {
+ AllowPause = false,
+ AllowLeadIn = false,
+ AllowResults = false
+ };
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ p.ScoreProcessor.AllJudged += () =>
+ {
+ if (currentPlayer == p) allJudgedFired = true;
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ allJudgedFired = false;
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for all judged", () => allJudgedFired);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs
index df903efe3d..e8b534bba9 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public class TestCaseSpinner : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
-{
+ {
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject)
@@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private bool auto;
- public TestDrawableSpinner(Spinner s, bool auto) : base(s)
+ public TestDrawableSpinner(Spinner s, bool auto)
+ : base(s)
{
this.auto = auto;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 273d29c3de..8c31db9a7d 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
new file mode 100644
index 0000000000..f6edd062e9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Configuration;
+
+namespace osu.Game.Rulesets.Osu.Configuration
+{
+ public class OsuRulesetConfigManager : RulesetConfigManager
+ {
+ public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
+ : base(settings, ruleset, variant)
+ {
+ }
+
+ protected override void InitialiseDefaults()
+ {
+ base.InitialiseDefaults();
+
+ Set(OsuRulesetSetting.SnakingInSliders, true);
+ Set(OsuRulesetSetting.SnakingOutSliders, true);
+ }
+ }
+
+ public enum OsuRulesetSetting
+ {
+ SnakingInSliders,
+ SnakingOutSliders
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index fd54dc0260..6e991a1d08 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double ApproachRate;
public double OverallDifficulty;
public int MaxCombo;
-
- public OsuDifficultyAttributes(Mod[] mods, double starRating)
- : base(mods, starRating)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3d0a1dc266..e2a1542574 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -1,10 +1,13 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
@@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyCalculator : DifficultyCalculator
{
- private const int section_length = 400;
private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
@@ -23,58 +25,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
- if (!beatmap.HitObjects.Any())
- return new OsuDifficultyAttributes(mods, 0);
-
- OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate);
- Skill[] skills =
- {
- new Aim(),
- new Speed()
- };
-
- double sectionLength = section_length * timeRate;
-
- // The first object doesn't generate a strain, so we begin with an incremented section end
- double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
-
- foreach (OsuDifficultyHitObject h in difficultyBeatmap)
- {
- while (h.BaseObject.StartTime > currentSectionEnd)
- {
- foreach (Skill s in skills)
- {
- s.SaveCurrentPeak();
- s.StartNewSectionFrom(currentSectionEnd);
- }
-
- currentSectionEnd += sectionLength;
- }
-
- foreach (Skill s in skills)
- s.Process(h);
- }
-
- // The peak strain will not be saved for the last section in the above loop
- foreach (Skill s in skills)
- s.SaveCurrentPeak();
+ if (beatmap.HitObjects.Count == 0)
+ return new OsuDifficultyAttributes { Mods = mods };
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
- double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
+ double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
+ double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
- return new OsuDifficultyAttributes(mods, starRating)
+ return new OsuDifficultyAttributes
{
+ StarRating = starRating,
+ Mods = mods,
AimStrain = aimRating,
SpeedStrain = speedRating,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
@@ -83,6 +54,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
};
}
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
+ {
+ // The first jump is formed by the first two hitobjects of the map.
+ // If the map has less than two OsuHitObjects, the enumerator will not return anything.
+ for (int i = 1; i < beatmap.HitObjects.Count; i++)
+ {
+ var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null;
+ var last = beatmap.HitObjects[i - 1];
+ var current = beatmap.HitObjects[i];
+
+ yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate);
+ }
+ }
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ {
+ new Aim(),
+ new Speed()
+ };
+
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new OsuModDoubleTime(),
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 83deac78a1..0dce5208dd 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -88,11 +88,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue()
{
- double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+ double rawAim = Attributes.AimStrain;
+
+ if (mods.Any(m => m is OsuModTouchDevice))
+ 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;
// Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
- (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
aimValue *= lengthBonus;
@@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
else if (Attributes.ApproachRate < 8.0f)
{
- approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
+ approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
@@ -121,8 +126,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
// Apply object-based bonus for flashlight.
aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
- (totalHits > 200 ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
- (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) : 0.0f);
+ (totalHits > 200
+ ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
+ (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f)
+ : 0.0f);
}
// Scale the aim value with accuracy _slightly_
@@ -139,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
- (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
speedValue *= Math.Pow(0.97f, countMiss);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
deleted file mode 100644
index 1736912e1f..0000000000
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using osu.Game.Rulesets.Osu.Objects;
-
-namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
-{
- ///
- /// An enumerable container wrapping input as
- /// which contains extra data required for difficulty calculation.
- ///
- public class OsuDifficultyBeatmap : IEnumerable
- {
- private readonly IEnumerator difficultyObjects;
-
- ///
- /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as
- /// which contains extra data required for difficulty calculation.
- ///
- public OsuDifficultyBeatmap(List objects, double timeRate)
- {
- // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
- // This should probably happen before the objects reach the difficulty calculator.
- difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
- }
-
- ///
- /// Returns an enumerator that enumerates all s in the .
- ///
- public IEnumerator GetEnumerator() => difficultyObjects;
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate)
- {
- // The first jump is formed by the first two hitobjects of the map.
- // If the map has less than two OsuHitObjects, the enumerator will not return anything.
- for (int i = 1; i < objects.Count; i++)
- yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 91d6c3d7a3..37276a3432 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -1,24 +1,20 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
- ///
- /// A wrapper around extending it with additional data required for difficulty calculation.
- ///
- public class OsuDifficultyHitObject
+ public class OsuDifficultyHitObject : DifficultyHitObject
{
private const int normalized_radius = 52;
- ///
- /// The this refers to.
- ///
- public OsuHitObject BaseObject { get; }
+ protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
///
/// Normalized distance from the end position of the previous to the start position of this .
@@ -31,31 +27,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
public double TravelDistance { get; private set; }
///
- /// Milliseconds elapsed since the StartTime of the previous .
+ /// Angle the player has to take to hit this .
+ /// Calculated as the angle between the circles (current-2, current-1, current).
///
- public double DeltaTime { get; private set; }
+ public double? Angle { get; private set; }
///
/// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms.
///
- public double StrainTime { get; private set; }
+ public readonly double StrainTime;
+ private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject;
- private readonly double timeRate;
- ///
- /// Initializes the object calculating extra data required for difficulty calculation.
- ///
- public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate)
+ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate)
+ : base(hitObject, lastObject, clockRate)
{
- this.lastObject = lastObject;
- this.timeRate = timeRate;
-
- BaseObject = currentObject;
+ this.lastLastObject = (OsuHitObject)lastLastObject;
+ this.lastObject = (OsuHitObject)lastObject;
setDistances();
- setTimingValues();
- // Calculate angle here
+
+ // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
+ StrainTime = Math.Max(50, DeltaTime);
}
private void setDistances()
@@ -68,34 +62,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
- Vector2 lastCursorPosition = lastObject.StackedPosition;
-
- var lastSlider = lastObject as Slider;
- if (lastSlider != null)
+ if (lastObject is Slider lastSlider)
{
computeSliderCursorPosition(lastSlider);
- lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
-
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
}
+ Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
+
// Don't need to jump to reach spinners
if (!(BaseObject is Spinner))
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
- }
- private void setTimingValues()
- {
- DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
+ if (lastLastObject != null)
+ {
+ Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject);
- // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
- StrainTime = Math.Max(50, DeltaTime);
+ Vector2 v1 = lastLastCursorPosition - lastObject.StackedPosition;
+ Vector2 v2 = BaseObject.StackedPosition - lastCursorPosition;
+
+ float dot = Vector2.Dot(v1, v2);
+ float det = v1.X * v2.Y - v1.Y * v2.X;
+
+ Angle = Math.Abs(Math.Atan2(det, dot));
+ }
}
private void computeSliderCursorPosition(Slider slider)
{
if (slider.LazyEndPosition != null)
return;
+
slider.LazyEndPosition = slider.StackedPosition;
float approxFollowCircleRadius = (float)(slider.Radius * 3);
@@ -125,7 +122,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime);
foreach (var time in scoringTimes)
computeVertex(time);
- computeVertex(slider.EndTime);
+ }
+
+ private Vector2 getEndCursorPosition(OsuHitObject hitObject)
+ {
+ Vector2 pos = hitObject.StackedPosition;
+
+ if (hitObject is Slider slider)
+ {
+ computeSliderCursorPosition(slider);
+ pos = slider.LazyEndPosition ?? pos;
+ }
+
+ return pos;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 9e5f13de62..e74f4933b2 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -2,7 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@@ -11,10 +14,46 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Aim : Skill
{
+ private const double angle_bonus_begin = Math.PI / 3;
+ private const double timing_threshold = 107;
+
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
- protected override double StrainValueOf(OsuDifficultyHitObject current)
- => (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime;
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ if (current.BaseObject is Spinner)
+ return 0;
+
+ var osuCurrent = (OsuDifficultyHitObject)current;
+
+ double result = 0;
+
+ if (Previous.Count > 0)
+ {
+ var osuPrevious = (OsuDifficultyHitObject)Previous[0];
+
+ if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
+ {
+ const double scale = 90;
+
+ var angleBonus = Math.Sqrt(
+ Math.Max(osuPrevious.JumpDistance - scale, 0)
+ * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
+ * Math.Max(osuCurrent.JumpDistance - scale, 0));
+ result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
+ }
+ }
+
+ double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
+ double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
+
+ return Math.Max(
+ result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
+ (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
+ );
+ }
+
+ private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 809632806b..46a81a9480 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@@ -10,30 +14,48 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : Skill
{
+ private const double single_spacing_threshold = 125;
+
+ private const double angle_bonus_begin = 5 * Math.PI / 6;
+ private const double pi_over_4 = Math.PI / 4;
+ private const double pi_over_2 = Math.PI / 2;
+
protected override double SkillMultiplier => 1400;
protected override double StrainDecayBase => 0.3;
- private const double single_spacing_threshold = 125;
- private const double stream_spacing_threshold = 110;
- private const double almost_diameter = 90;
+ private const double min_speed_bonus = 75; // ~200BPM
+ private const double max_speed_bonus = 45; // ~330BPM
+ private const double speed_balancing_factor = 40;
- protected override double StrainValueOf(OsuDifficultyHitObject current)
+ protected override double StrainValueOf(DifficultyHitObject current)
{
- double distance = current.TravelDistance + current.JumpDistance;
+ if (current.BaseObject is Spinner)
+ return 0;
- double speedValue;
- if (distance > single_spacing_threshold)
- speedValue = 2.5;
- else if (distance > stream_spacing_threshold)
- speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
- else if (distance > almost_diameter)
- speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
- else if (distance > almost_diameter / 2)
- speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2);
- else
- speedValue = 0.95;
+ var osuCurrent = (OsuDifficultyHitObject)current;
- return speedValue / current.StrainTime;
+ double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
+ double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
+
+ double speedBonus = 1.0;
+ if (deltaTime < min_speed_bonus)
+ speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
+
+ double angleBonus = 1.0;
+ if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin)
+ {
+ angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57;
+ if (osuCurrent.Angle.Value < pi_over_2)
+ {
+ angleBonus = 1.28;
+ if (distance < 90 && osuCurrent.Angle.Value < pi_over_4)
+ angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
+ else if (distance < 90)
+ angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4);
+ }
+ }
+
+ return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
index 5958b32f33..7f6a60c400 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
- ScaleBindable.BindValueChanged(v => Scale = new Vector2(v), true);
+ ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
}
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index 0dcf5ba551..a4050f0c31 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -5,6 +5,7 @@ using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
@@ -22,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
base.LoadComplete();
- // Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame
- HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position;
+ // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
+ HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
}
protected override bool OnClick(ClickEvent e)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs
index 44e61f30b0..315a5a2b9d 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs
index dfc8bf4664..8fd1d6d6f9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index a164b7e6bb..e257369ad9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
path = new SmoothPath
{
Anchor = Anchor.Centre,
- PathWidth = 1
+ PathRadius = 1
},
marker = new CircularContainer
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index cb9b5211d4..957550a051 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
InternalChild = body = new ManualSliderBody
{
AccentColour = Color4.Transparent,
- PathWidth = slider.Scale * 64
+ PathRadius = slider.Scale * 64
};
}
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
- ScaleBindable.BindValueChanged(v => body.PathWidth = v * 64, true);
+ ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true);
}
private void updatePosition() => Position = slider.StackedPosition;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs
index d2fec859b8..9d164ebe0b 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index f563ad2264..989a53db1f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -47,6 +47,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
setState(PlacementState.Initial);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
+ HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
+ }
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
switch (state)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
index 7f91bc49eb..ae94848c81 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
PositionBindable.BindValueChanged(_ => updatePosition(), true);
StackHeightBindable.BindValueChanged(_ => updatePosition());
- ScaleBindable.BindValueChanged(v => ring.Scale = new Vector2(v), true);
+ ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
}
private void updatePosition() => Position = spinner.Position;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
similarity index 58%
rename from osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index c293a5a4f8..1a6e78d918 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -9,15 +9,18 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
- public class OsuEditRulesetContainer : OsuRulesetContainer
+ public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
- public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
- protected override CursorContainer CreateCursor() => null;
+ protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor { Size = Vector2.One };
- protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One };
+ private class OsuPlayfieldNoCursor : OsuPlayfield
+ {
+ protected override CursorContainer CreateCursor() => null;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 174321b8b9..dd3925e04f 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
- => new OsuEditRulesetContainer(ruleset, beatmap);
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
+ => new DrawableOsuEditRuleset(ruleset, beatmap);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
diff --git a/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs b/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs
index d7c76fccbe..ad292b0439 100644
--- a/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs
@@ -9,8 +9,10 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
[Description(@"")]
None,
+
[Description(@"Good")]
Good,
+
[Description(@"Amazing")]
Perfect
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
index d7ba0d4da9..bea2bbcb32 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
- protected override Score CreateReplayScore(Beatmap beatmap) => new Score
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
Replay = new OsuAutoGenerator(beatmap).Generate()
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 19b627b560..a1f4dfe1da 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -18,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModBlinds : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor
+ public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor
{
public override string Name => "Blinds";
public override string Description => "Play with blinds on your screen.";
@@ -32,14 +32,14 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1.12;
private DrawableOsuBlinds blinds;
- public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- rulesetContainer.Overlays.Add(blinds = new DrawableOsuBlinds(rulesetContainer.Playfield.HitObjectContainer, rulesetContainer.Beatmap));
+ drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
- scoreProcessor.Health.ValueChanged += val => { blinds.AnimateClosedness((float)val); };
+ scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
}
///
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index ba82465a78..2c40d18f1b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
@@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Osu.Mods
return default_flashlight_size;
}
- protected override void OnComboChange(int newCombo)
+ protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
new file mode 100644
index 0000000000..65e9eb7a1d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -0,0 +1,74 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Grow";
+
+ public override string Acronym => "GR";
+
+ public override FontAwesome Icon => FontAwesome.fa_arrows_v;
+
+ public override ModType Type => ModType.Fun;
+
+ public override string Description => "Hit them at the right size!";
+
+ public override double ScoreMultiplier => 1;
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables)
+ {
+ switch (drawable)
+ {
+ case DrawableSpinner _:
+ continue;
+ default:
+ drawable.ApplyCustomUpdateState += ApplyCustomState;
+ break;
+ }
+ }
+ }
+
+ protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
+ {
+ var h = (OsuHitObject)drawable.HitObject;
+
+ // apply grow effect
+ switch (drawable)
+ {
+ case DrawableSliderHead _:
+ case DrawableSliderTail _:
+ // special cases we should *not* be scaling.
+ break;
+ case DrawableSlider _:
+ case DrawableHitCircle _:
+ {
+ using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine);
+ break;
+ }
+ }
+
+ // remove approach circles
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ // we don't want to see the approach circle
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ circle.ApproachCircle.Hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index efcab28310..ec23570f54 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -13,7 +13,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer
+ public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset
{
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
@@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods
state.Apply(osuInputManager.CurrentState, osuInputManager);
}
- public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
// grab the input manager for future use.
- osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager;
+ osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
osuInputManager.AllowUserPresses = false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
new file mode 100644
index 0000000000..571756d056
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModTouchDevice : Mod
+ {
+ public override string Name => "Touch Device";
+ public override string Acronym => "TD";
+ public override double ScoreMultiplier => 1;
+
+ public override bool Ranked => true;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 959bf1dc77..9a769ec39c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
foreach (var drawable in drawables)
{
- var hitObject = (OsuHitObject) drawable.HitObject;
+ var hitObject = (OsuHitObject)drawable.HitObject;
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
.MoveTo(originalPosition, moveDuration, Easing.InOutSine);
}
- theta += (float) hitObject.TimeFadeIn / 1000;
+ theta += (float)hitObject.TimeFadeIn / 1000;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs
index 0e88e1094e..9106f4c7bd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// Connects hit objects visually, for example with follow points.
///
- public abstract class ConnectionRenderer : Container
+ public abstract class ConnectionRenderer : LifetimeManagementContainer
where T : HitObject
{
///
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 4c0f646ad5..8f9d487d49 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -12,39 +12,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
public class FollowPointRenderer : ConnectionRenderer
{
private int pointDistance = 32;
+
///
/// Determines how much space there is between points.
///
public int PointDistance
{
- get { return pointDistance; }
+ get => pointDistance;
set
{
if (pointDistance == value) return;
+
pointDistance = value;
update();
}
}
private int preEmpt = 800;
+
///
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
///
public int PreEmpt
{
- get { return preEmpt; }
+ get => preEmpt;
set
{
if (preEmpt == value) return;
+
preEmpt = value;
update();
}
}
private IEnumerable hitObjects;
+
public override IEnumerable HitObjects
{
- get { return hitObjects; }
+ get => hitObjects;
set
{
hitObjects = value;
@@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void update()
{
- Clear();
+ ClearInternal();
if (hitObjects == null)
return;
@@ -86,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
- Add(fp = new FollowPoint
+ AddInternal(fp = new FollowPoint
{
Position = pointStartPosition,
Rotation = rotation,
@@ -107,6 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
fp.Expire(true);
}
}
+
prevHitObject = currHitObject;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index df0769982d..decd0ce073 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -3,8 +3,9 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osuTK;
@@ -27,40 +28,60 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable stackHeightBindable = new Bindable();
private readonly IBindable scaleBindable = new Bindable();
+ public OsuAction? HitAction => circle.HitAction;
+
+ private readonly Container explodeContainer;
+
+ private readonly Container scaleContainer;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
Origin = Anchor.Centre;
Position = HitObject.StackedPosition;
- Scale = new Vector2(h.Scale);
InternalChildren = new Drawable[]
{
- glow = new GlowPiece(),
- circle = new CirclePiece
+ scaleContainer = new Container
{
- Hit = () =>
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Child = explodeContainer = new Container
{
- if (AllJudged)
- return false;
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ glow = new GlowPiece(),
+ circle = new CirclePiece
+ {
+ Hit = () =>
+ {
+ if (AllJudged)
+ return false;
- UpdateResult(true);
- return true;
- },
+ UpdateResult(true);
+ return true;
+ },
+ },
+ number = new NumberPiece
+ {
+ Text = (HitObject.IndexInCurrentCombo + 1).ToString(),
+ },
+ ring = new RingPiece(),
+ flash = new FlashPiece(),
+ explode = new ExplodePiece(),
+ ApproachCircle = new ApproachCircle
+ {
+ Alpha = 0,
+ Scale = new Vector2(4),
+ }
+ }
+ }
},
- number = new NumberPiece
- {
- Text = (HitObject.IndexInCurrentCombo + 1).ToString(),
- },
- ring = new RingPiece(),
- flash = new FlashPiece(),
- explode = new ExplodePiece(),
- ApproachCircle = new ApproachCircle
- {
- Alpha = 0,
- Scale = new Vector2(4),
- }
};
//may not be so correct
@@ -72,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
- scaleBindable.BindValueChanged(v => Scale = new Vector2(v));
+ scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
@@ -81,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
@@ -118,6 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
+ ApproachCircle.Expire(true);
}
protected override void UpdateCurrentState(ArmedState state)
@@ -131,6 +153,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
+ circle.HitAction = null;
+
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss);
break;
@@ -156,8 +180,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.FadeOut();
number.FadeOut();
- this.FadeOut(800)
- .ScaleTo(Scale * 1.5f, 400, Easing.OutQuad);
+ this.FadeOut(800);
+ explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad);
}
Expire();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 2608706264..10b37af957 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
- AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : Color4.White);
+ AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index 2512e74da7..938a2293ba 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osuTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -16,12 +15,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
}
- protected override void LoadComplete()
+ protected override void ApplyHitAnimations()
{
- if (Result.Type != HitResult.Miss)
- JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
-
- base.LoadComplete();
+ JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
+ base.ApplyHitAnimations();
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 1a1b0530d8..57ea0abdd8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -8,10 +8,10 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
-using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -33,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable scaleBindable = new Bindable();
private readonly IBindable pathBindable = new Bindable();
+ [Resolved(CanBeNull = true)]
+ private OsuRulesetConfigManager config { get; set; }
+
public DrawableSlider(Slider s)
: base(s)
{
@@ -47,12 +50,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SnakingSliderBody(s)
{
- PathWidth = s.Scale * 64,
+ PathRadius = s.Scale * 64,
},
ticks = new Container { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
{
+ GetInitialHitAction = () => HeadCircle.HitAction,
BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale),
AlwaysPresent = true,
@@ -93,16 +97,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
+ private void load()
{
- config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn);
- config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut);
+ config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
+ config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
- scaleBindable.BindValueChanged(v =>
+ scaleBindable.BindValueChanged(scale =>
{
- Body.PathWidth = HitObject.Scale * 64;
- Ball.Scale = new Vector2(HitObject.Scale);
+ Body.PathRadius = scale.NewValue * 64;
+ Ball.Scale = new Vector2(scale.NewValue);
});
positionBindable.BindTo(HitObject.PositionBindable);
@@ -114,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
@@ -156,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.SkinChanged(skin, allowFallback);
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : Body.AccentColour);
- Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : Body.BorderColour);
- Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : Ball.AccentColour);
+ Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? Body.AccentColour;
+ Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Body.BorderColour;
+ Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Ball.AccentColour;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index e24efc6ffb..66b6f0f9ac 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -3,7 +3,7 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index 53f6aa5b76..23c5494cf5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 1105e8525b..b5ce36f889 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => false;
- public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
+ public DrawableSliderTick(SliderTick sliderTick)
+ : base(sliderTick)
{
Size = new Vector2(16) * sliderTick.Scale;
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index c411b562e4..789af4f49b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -11,7 +11,7 @@ using osuTK.Graphics;
using osu.Game.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
@@ -42,7 +42,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Color4 normalColour;
private Color4 completeColour;
- public DrawableSpinner(Spinner s) : base(s)
+ public DrawableSpinner(Spinner s)
+ : base(s)
{
Origin = Anchor.Centre;
Position = s.Position;
@@ -130,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark;
- positionBindable.BindValueChanged(v => Position = v);
+ positionBindable.BindValueChanged(pos => Position = pos.NewValue);
positionBindable.BindTo(HitObject.PositionBindable);
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 8ed99ca660..8ee065aaea 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class ApproachCircle : Container
{
+ public override bool RemoveWhenNotAlive => false;
+
public ApproachCircle()
{
Anchor = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index b7e2748ca9..786cac7198 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Func Hit;
+ public OsuAction? HitAction;
+
public CirclePiece()
{
Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2);
@@ -35,7 +37,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
- return IsHovered && (Hit?.Invoke() ?? false);
+ if (IsHovered && (Hit?.Invoke() ?? false))
+ {
+ HitAction = action;
+ return true;
+ }
+
+ break;
}
return false;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index bc354bc2b6..93ac8748dd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -17,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public string Text
{
- get { return number.Text; }
- set { number.Text = value; }
+ get => number.Text;
+ set => number.Text = value;
}
public NumberPiece()
@@ -42,9 +43,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
{
- Font = @"Venera",
+ Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
- TextSize = 40,
}, restrictSize: false)
{
Text = @"1"
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index db9fee242a..e41c568403 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -19,12 +20,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private Color4 accentColour = Color4.Black;
+ public Func GetInitialHitAction;
+
///
/// The colour that is used for the slider ball.
///
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
accentColour = value;
@@ -129,15 +132,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
base.ClearTransformsAfter(time, false, targetMember);
}
+ public override void ApplyTransformsAt(double time, bool propagateChildren = false)
+ {
+ // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
+ base.ApplyTransformsAt(time, false);
+ }
+
private bool tracking;
public bool Tracking
{
- get { return tracking; }
+ get => tracking;
private set
{
if (value == tracking)
return;
+
tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint);
@@ -145,20 +155,72 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
- private bool canCurrentlyTrack => Time.Current >= slider.StartTime && Time.Current < slider.EndTime;
+ ///
+ /// If the cursor moves out of the ball's radius we still need to be able to receive positional updates to stop tracking.
+ ///
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ ///
+ /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle.
+ ///
+ /// This is a requirement to stop the case where a player holds down one key (from before the slider) and taps the second key while maintaining full scoring (tracking) of sliders.
+ /// Visually, this special case can be seen below (time increasing from left to right):
+ ///
+ /// Z Z+X Z
+ /// o========o
+ ///
+ /// Without this logic, tracking would continue through the entire slider even though no key hold action is directly attributing to it.
+ ///
+ /// In all other cases, no special handling is required (either key being pressed is allowable as valid tracking).
+ ///
+ /// The reason for storing this as a time value (rather than a bool) is to correctly handle rewind scenarios.
+ ///
+ private double? timeToAcceptAnyKeyAfter;
protected override void Update()
{
base.Update();
- if (Time.Current < slider.EndTime)
+ // from the point at which the head circle is hit, this will be non-null.
+ // it may be null if the head circle was missed.
+ var headCircleHitAction = GetInitialHitAction();
+
+ if (headCircleHitAction == null)
+ timeToAcceptAnyKeyAfter = null;
+
+ var actions = drawableSlider?.OsuActionInputManager?.PressedActions;
+
+ // if the head circle was hit with a specific key, tracking should only occur while that key is pressed.
+ if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null)
{
- // Make sure to use the base version of ReceivePositionalInputAt so that we correctly check the position.
- Tracking = canCurrentlyTrack
- && lastScreenSpaceMousePosition.HasValue
- && ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value)
- && (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
+ var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton;
+
+ // we can return to accepting all keys if the initial head circle key is the *only* key pressed, or all keys have been released.
+ if (actions?.Contains(otherKey) != true)
+ timeToAcceptAnyKeyAfter = Time.Current;
}
+
+ Tracking =
+ // in valid time range
+ Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
+ // in valid position range
+ lastScreenSpaceMousePosition.HasValue && base.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
+ // valid action
+ (actions?.Any(isValidTrackingAction) ?? false);
+ }
+
+ ///
+ /// Check whether a given user input is a valid tracking action.
+ ///
+ private bool isValidTrackingAction(OsuAction action)
+ {
+ bool headCircleHit = GetInitialHitAction().HasValue;
+
+ // if the head circle was hit, we may not yet be allowed to accept any key, so we must use the initial hit action.
+ if (headCircleHit && (!timeToAcceptAnyKeyAfter.HasValue || Time.Current <= timeToAcceptAnyKeyAfter.Value))
+ return action == GetInitialHitAction();
+
+ return action == OsuAction.LeftButton || action == OsuAction.RightButton;
}
public void UpdateProgress(double completionProgress)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 6d2dc220da..2f5c326bda 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private readonly BufferedContainer container;
- public float PathWidth
+ public float PathRadius
{
- get => path.PathWidth;
- set => path.PathWidth = value;
+ get => path.PathRadius;
+ set => path.PathRadius = value;
}
///
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (path.AccentColour == value)
return;
+
path.AccentColour = value;
container.ForceRedraw();
@@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (path.BorderColour == value)
return;
+
path.BorderColour = value;
container.ForceRedraw();
@@ -105,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (borderColour == value)
return;
+
borderColour = value;
InvalidateTexture();
@@ -120,6 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (accentColour == value)
return;
+
accentColour = value;
InvalidateTexture();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index 256cd088de..73b184bffe 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -54,18 +54,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
- double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
+ double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{
start = 0;
- end = SnakingOut ? spanProgress : 1;
+ end = SnakingOut.Value ? spanProgress : 1;
}
else
{
- start = SnakingOut ? spanProgress : 0;
+ start = SnakingOut.Value ? spanProgress : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
index 0d970f4c2c..c982f53c2b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
@@ -15,10 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Color4 AccentColour
{
- get
- {
- return Disc.Colour;
- }
+ get => Disc.Colour;
set
{
Disc.Colour = value;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 4206852b6c..448a2eada7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Color4 AccentColour
{
- get { return background.AccentColour; }
- set { background.AccentColour = value; }
+ get => background.AccentColour;
+ set => background.AccentColour = value;
}
private readonly SpinnerBackground background;
@@ -43,12 +43,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private bool tracking;
+
public bool Tracking
{
- get { return tracking; }
+ get => tracking;
set
{
if (value == tracking) return;
+
tracking = value;
background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100);
@@ -56,12 +58,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
private bool complete;
+
public bool Complete
{
- get { return complete; }
+ get => complete;
set
{
if (value == complete) return;
+
complete = value;
updateCompleteTick();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
index e4d09b9306..b1d90c49f6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -23,16 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = @"0",
- Font = @"Venera",
- TextSize = 24
+ Font = OsuFont.Numeric.With(size: 24)
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = @"SPINS PER MINUTE",
- Font = @"Venera",
- TextSize = 12,
+ Font = OsuFont.Numeric.With(size: 12),
Y = 30
}
};
@@ -42,10 +41,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double SpinsPerMinute
{
- get { return spm; }
+ get => spm;
private set
{
if (value == spm) return;
+
spm = value;
spmText.Text = Math.Truncate(value).ToString(@"#0");
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index a2e518ace4..364c182dd4 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual Vector2 Position
{
- get => PositionBindable;
+ get => PositionBindable.Value;
set => PositionBindable.Value = value;
}
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public int StackHeight
{
- get => StackHeightBindable;
+ get => StackHeightBindable.Value;
set => StackHeightBindable.Value = value;
}
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public float Scale
{
- get => ScaleBindable;
+ get => ScaleBindable.Value;
set => ScaleBindable.Value = value;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index d47fed354a..1afbacc01e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -1,14 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Caching;
-using osu.Framework.Configuration;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -155,115 +154,76 @@ namespace osu.Game.Rulesets.Osu.Objects
{
base.CreateNestedHitObjects();
- createSliderEnds();
- createTicks();
- createRepeatPoints();
-
- if (LegacyLastTickOffset != null)
- TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value);
- }
-
- private void createSliderEnds()
- {
- HeadCircle = new SliderCircle
+ foreach (var e in
+ SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
- StartTime = StartTime,
- Position = Position,
- Samples = getNodeSamples(0),
- SampleControlPoint = SampleControlPoint,
- IndexInCurrentCombo = IndexInCurrentCombo,
- ComboIndex = ComboIndex,
- };
+ var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL)
+ ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
+ var sampleList = new List();
- TailCircle = new SliderTailCircle(this)
- {
- StartTime = EndTime,
- Position = EndPosition,
- IndexInCurrentCombo = IndexInCurrentCombo,
- ComboIndex = ComboIndex,
- };
-
- AddNested(HeadCircle);
- AddNested(TailCircle);
- }
-
- private void createTicks()
- {
- // A very lenient maximum length of a slider for ticks to be generated.
- // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
- const double max_length = 100000;
-
- var length = Math.Min(max_length, Path.Distance);
- var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
-
- if (tickDistance == 0) return;
-
- var minDistanceFromEnd = Velocity * 10;
-
- var spanCount = this.SpanCount();
-
- for (var span = 0; span < spanCount; span++)
- {
- var spanStartTime = StartTime + span * SpanDuration;
- var reversed = span % 2 == 1;
-
- for (var d = tickDistance; d <= length; d += tickDistance)
- {
- if (d > length - minDistanceFromEnd)
- break;
-
- var distanceProgress = d / length;
- var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
-
- var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL)
- ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
- var sampleList = new List();
-
- if (firstSample != null)
- sampleList.Add(new SampleInfo
- {
- Bank = firstSample.Bank,
- Volume = firstSample.Volume,
- Name = @"slidertick",
- });
-
- AddNested(new SliderTick
+ if (firstSample != null)
+ sampleList.Add(new SampleInfo
{
- SpanIndex = span,
- SpanStartTime = spanStartTime,
- StartTime = spanStartTime + timeProgress * SpanDuration,
- Position = Position + Path.PositionAt(distanceProgress),
- StackHeight = StackHeight,
- Scale = Scale,
- Samples = sampleList
+ Bank = firstSample.Bank,
+ Volume = firstSample.Volume,
+ Name = @"slidertick",
});
+
+ switch (e.Type)
+ {
+ case SliderEventType.Tick:
+ AddNested(new SliderTick
+ {
+ SpanIndex = e.SpanIndex,
+ SpanStartTime = e.SpanStartTime,
+ StartTime = e.Time,
+ Position = Position + Path.PositionAt(e.PathProgress),
+ StackHeight = StackHeight,
+ Scale = Scale,
+ Samples = sampleList
+ });
+ break;
+ case SliderEventType.Head:
+ AddNested(HeadCircle = new SliderCircle
+ {
+ StartTime = e.Time,
+ Position = Position,
+ Samples = getNodeSamples(0),
+ SampleControlPoint = SampleControlPoint,
+ IndexInCurrentCombo = IndexInCurrentCombo,
+ ComboIndex = ComboIndex,
+ });
+ break;
+ case SliderEventType.LegacyLastTick:
+ // we need to use the LegacyLastTick here for compatibility reasons (difficulty).
+ // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay.
+ // if this is to change, we should revisit this.
+ AddNested(TailCircle = new SliderTailCircle(this)
+ {
+ StartTime = e.Time,
+ Position = EndPosition,
+ IndexInCurrentCombo = IndexInCurrentCombo,
+ ComboIndex = ComboIndex,
+ });
+ break;
+ case SliderEventType.Repeat:
+ AddNested(new RepeatPoint
+ {
+ RepeatIndex = e.SpanIndex,
+ SpanDuration = SpanDuration,
+ StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
+ Position = Position + Path.PositionAt(e.PathProgress),
+ StackHeight = StackHeight,
+ Scale = Scale,
+ Samples = getNodeSamples(e.SpanIndex + 1)
+ });
+ break;
}
}
}
- private void createRepeatPoints()
- {
- for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++)
- {
- AddNested(new RepeatPoint
- {
- RepeatIndex = repeatIndex,
- SpanDuration = SpanDuration,
- StartTime = StartTime + repeat * SpanDuration,
- Position = Position + Path.PositionAt(repeat % 2),
- StackHeight = StackHeight,
- Scale = Scale,
- Samples = getNodeSamples(1 + repeatIndex)
- });
- }
- }
-
- private List getNodeSamples(int nodeIndex)
- {
- if (nodeIndex < NodeSamples.Count)
- return NodeSamples[nodeIndex];
- return Samples;
- }
+ private List getNodeSamples(int nodeIndex) =>
+ nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 29b00b8d5d..4f2af64161 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -1,13 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects
{
+ ///
+ /// Note that this should not be used for timing correctness.
+ /// See usage in for more information.
+ ///
public class SliderTailCircle : SliderCircle
{
private readonly IBindable pathBindable = new Bindable();
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index 2dd34d7d40..b9e083d35b 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu
protected override bool Handle(UIEvent e)
{
if (!AllowUserPresses) return false;
+
return base.Handle(e);
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 9415adc411..7d3aff7801 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -13,11 +13,15 @@ using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Scoring;
@@ -25,7 +29,7 @@ namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
@@ -82,6 +86,9 @@ namespace osu.Game.Rulesets.Osu
if (mods.HasFlag(LegacyMods.Target))
yield return new OsuModTarget();
+
+ if (mods.HasFlag(LegacyMods.TouchDevice))
+ yield return new OsuModTouchDevice();
}
public override IEnumerable GetModsFor(ModType type)
@@ -118,9 +125,12 @@ namespace osu.Game.Rulesets.Osu
new OsuModAutopilot(),
};
case ModType.Fun:
- return new Mod[] {
+ return new Mod[]
+ {
new OsuModTransform(),
new OsuModWiggle(),
+ new OsuModGrow(),
+ new MultiMod(new ModWindUp(), new ModWindDown()),
};
default:
return new Mod[] { };
@@ -139,12 +149,14 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu";
- public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
+ public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override int? LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
+ public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
+
public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
index f37e8cb946..b9a7096330 100644
--- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
@@ -9,3 +9,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.iOS")]
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index b024ff4b05..c1aaa7767e 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -10,12 +10,15 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays
{
public class OsuAutoGenerator : OsuAutoGeneratorBase
{
+ public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
+
#region Parameters
///
@@ -42,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Replays
#region Construction / Initialisation
- public OsuAutoGenerator(Beatmap beatmap)
+ public OsuAutoGenerator(IBeatmap beatmap)
: base(beatmap)
{
// Already superhuman, but still somewhat realistic
@@ -209,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Replays
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
- timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
+ timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
{
// Perform eased movement
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
index 8bcdb5ee41..9ab358ee12 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
@@ -3,7 +3,6 @@
using osuTK;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Replays;
@@ -12,7 +11,7 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.Replays
{
- public abstract class OsuAutoGeneratorBase : AutoGenerator
+ public abstract class OsuAutoGeneratorBase : AutoGenerator
{
#region Constants
@@ -20,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// Constants (for spinners).
///
protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
+
protected const float SPIN_RADIUS = 50;
///
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Replays
protected Replay Replay;
protected List Frames => Replay.Frames;
- protected OsuAutoGeneratorBase(Beatmap beatmap)
+ protected OsuAutoGeneratorBase(IBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay();
@@ -46,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Replays
#endregion
#region Utilities
+
protected double ApplyModsToTime(double v) => v;
protected double ApplyModsToRate(double v) => v;
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
rename to osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
index 0e9a906d85..d1ac77857d 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
@@ -11,9 +11,9 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Replays
{
- public class OsuReplayInputHandler : FramedReplayInputHandler
+ public class OsuFramedReplayInputHandler : FramedReplayInputHandler
{
- public OsuReplayInputHandler(Replay replay)
+ public OsuFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu
new file mode 100644
index 0000000000..bf345811a2
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu
@@ -0,0 +1,179 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.3
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+62500,-500,4,2,1,50,0,0
+71000,-100,4,2,1,50,0,0
+
+[HitObjects]
+// Circles spaced 1 beat apart, with increasing jump distance
+126,112,500,5,0,0:0:0:0:
+130,155,1000,1,0,0:0:0:0:
+131,269,1500,1,0,0:0:0:0:
+341,269,2000,1,0,0:0:0:0:
+113,95,2500,1,0,0:0:0:0:
+
+// Circles spaced 1/2 beat apart, with increasing jump distance
+108,104,3500,5,0,0:0:0:0:
+110,145,3750,1,0,0:0:0:0:
+115,262,4000,1,0,0:0:0:0:
+285,265,4250,1,0,0:0:0:0:
+458,48,4500,1,0,0:0:0:0:
+35,199,4750,1,0,0:0:0:0:
+251,340,5000,1,0,0:0:0:0:
+20,352,5250,1,0,0:0:0:0:
+426,62,5500,1,0,0:0:0:0:
+
+// Circles spaced 1/4 beat apart, with increasing jump distances
+211,138,6500,5,0,0:0:0:0:
+99,256,6625,1,0,0:0:0:0:
+68,129,6750,1,0,0:0:0:0:
+371,340,6875,1,0,0:0:0:0:
+241,219,7000,1,0,0:0:0:0:
+252,148,7125,1,0,0:0:0:0:
+434,97,7250,1,0,0:0:0:0:
+40,38,7375,1,0,0:0:0:0:
+114,334,7500,1,0,0:0:0:0:
+301,19,7625,1,0,0:0:0:0:
+441,241,7750,1,0,0:0:0:0:
+121,91,7875,1,0,0:0:0:0:
+270,384,8000,1,0,0:0:0:0:
+488,92,8125,1,0,0:0:0:0:
+332,82,8250,1,0,0:0:0:0:
+108,240,8375,1,0,0:0:0:0:
+281,268,8500,1,0,0:0:0:0:
+
+// Constant spaced circles spaced 1/2 beat apart, small jump distances, changing angles
+252,191,9500,5,0,0:0:0:0:
+356,191,9750,1,0,0:0:0:0:
+311,268,10000,1,0,0:0:0:0:
+190,270,10250,1,0,0:0:0:0:
+107,199,10500,1,0,0:0:0:0:
+172,105,10750,1,0,0:0:0:0:
+297,102,11000,1,0,0:0:0:0:
+373,178,11250,1,0,0:0:0:0:
+252,195,11500,1,0,0:0:0:0:
+
+// Constant spaced circles spaced 1/2 beat apart, large jump distances, changing angles
+140,187,12500,5,0,0:0:0:0:
+451,331,12750,1,0,0:0:0:0:
+46,338,13000,1,0,0:0:0:0:
+204,50,13250,1,0,0:0:0:0:
+464,162,13500,1,0,0:0:0:0:
+252,346,13750,1,0,0:0:0:0:
+13,175,14000,1,0,0:0:0:0:
+488,181,14250,1,0,0:0:0:0:
+251,187,14500,1,0,0:0:0:0:
+
+// Constant spaced circles spaced 1/4 beat apart, small jump distances, changing angles
+188,192,15500,5,0,0:0:0:0:
+298,194,15625,1,0,0:0:0:0:
+317,84,15750,1,0,0:0:0:0:
+185,85,15875,1,0,0:0:0:0:
+77,200,16000,1,0,0:0:0:0:
+184,303,16125,1,0,0:0:0:0:
+295,225,16250,1,0,0:0:0:0:
+300,84,16375,1,0,0:0:0:0:
+144,82,16500,1,0,0:0:0:0:
+141,215,16625,1,0,0:0:0:0:
+314,184,16750,1,0,0:0:0:0:
+188,192,16875,1,0,0:0:0:0:
+188,192,17000,1,0,0:0:0:0:
+
+// Constant spaced circles spaced 1/4 beat apart, large jump distances, changing angles
+97,192,18000,5,0,0:0:0:0:
+336,38,18125,1,0,0:0:0:0:
+440,322,18250,1,0,0:0:0:0:
+39,331,18375,1,0,0:0:0:0:
+98,39,18500,1,0,0:0:0:0:
+460,179,18625,1,0,0:0:0:0:
+245,338,18750,1,0,0:0:0:0:
+12,184,18875,1,0,0:0:0:0:
+250,41,19000,1,0,0:0:0:0:
+265,193,19125,1,0,0:0:0:0:
+486,22,19250,1,0,0:0:0:0:
+411,205,19375,1,0,0:0:0:0:
+107,198,19500,1,0,0:0:0:0:
+
+// Short sliders spaced 1 beat apart
+28,108,20500,2,0,L|196:107,1,160
+25,177,21500,2,0,L|193:176,1,160
+26,308,22500,2,0,L|194:307,1,160
+320,89,23500,2,0,L|488:88,1,160
+
+// Short sliders spaced 1/2 beat apart
+28,108,25000,6,0,L|196:107,1,160
+27,173,25750,2,0,L|195:172,1,160
+25,292,26500,2,0,L|193:291,1,160
+340,213,27250,2,0,L|508:212,1,160
+21,44,28000,2,0,L|189:43,1,160
+
+// Short sliders spaced 1/4 beat apart
+28,108,29500,6,0,L|196:107,1,160
+30,169,30125,2,0,L|198:168,1,160
+35,282,30750,2,0,L|203:281,1,160
+327,286,31375,2,0,L|495:285,1,160
+51,61,32000,2,0,L|219:60,1,160
+
+// Large, medium-paced slider shapes
+// PerfectCurve
+66,86,33500,6,0,P|246:348|427:44,1,800
+66,86,36500,2,0,P|246:348|427:44,1,800
+66,86,39500,2,0,P|246:348|427:44,1,800
+// Linear
+66,72,42500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
+66,72,46500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
+66,72,50500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
+// Bezier
+76,287,54500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
+76,287,57000,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
+76,287,59500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
+
+// Large slow slider with many ticks
+81,170,62500,6,0,P|263:78|168:268,1,480
+
+// Fast slider with many repeats
+102,152,71000,6,0,L|175:153,18,64
+
+// Slider-circle combos, spaced 1/2 beat apart
+106,204,75500,6,0,P|275:33|171:304,1,800
+255,179,78250,1,0,0:0:0:0:
+106,204,78500,2,0,P|275:33|171:304,1,800
+255,179,81250,1,0,0:0:0:0:
+106,204,81500,2,0,P|275:33|171:304,1,800
+
+// Circle-spinner combos, spaced 1/2 beat apart
+82,69,85000,5,0,0:0:0:0:
+256,192,85250,8,0,86000,0:0:0:0:
+384,189,86250,5,0,0:0:0:0:
+256,192,86500,12,0,87000,0:0:0:0:
+
+// Spinner-spinner combos, spaced 1/2 beat apart
+256,192,88000,12,0,89000,0:0:0:0:
+256,192,89250,12,0,90250,0:0:0:0:
+256,192,90500,12,0,91500,0:0:0:0:
+256,192,91750,12,0,92750,0:0:0:0:
+256,192,93000,12,0,94000,0:0:0:0:
+
+// Slider-spinner combos, spaced 1/2 beat apart
+49,89,95000,6,0,L|214:87,1,160
+256,192,95625,12,0,96500,0:0:0:0:
+12,299,96625,6,0,L|177:297,1,160
+256,192,97250,12,0,98125,0:0:0:0:
+295,107,98250,6,0,L|460:105,1,160
+256,192,98875,12,0,99750,0:0:0:0:
+279,325,99875,6,0,L|444:323,1,160
+256,192,100500,12,0,101375,0:0:0:0:
+197,197,101500,6,0,L|362:195,1,160
+256,192,102125,12,0,103000,0:0:0:0:
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 777588d6d7..2c8bf11016 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
internal class OsuScoreProcessor : ScoreProcessor
{
- public OsuScoreProcessor(RulesetContainer rulesetContainer)
- : base(rulesetContainer)
+ public OsuScoreProcessor(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
{
}
@@ -74,6 +74,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
- protected override HitWindows CreateHitWindows() => new OsuHitWindows();
+ public override HitWindows CreateHitWindows() => new OsuHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 03030121d3..03dbf7ac63 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
private int currentIndex;
- private Shader shader;
+ private IShader shader;
private Texture texture;
private Vector2 size => texture.Size * Scale;
@@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override bool IsPresent => true;
- private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
private const int max_sprites = 2048;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
@@ -55,7 +54,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
tNode.Texture = texture;
tNode.Size = size;
tNode.Time = time;
- tNode.Shared = trailDrawNodeSharedData;
for (int i = 0; i < parts.Length; ++i)
if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID)
@@ -81,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, TextureStore textures)
{
- shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
+ shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
texture = textures.Get(@"Cursor/cursortrail");
Scale = new Vector2(1 / texture.ScaleAdjust);
}
@@ -167,22 +165,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public bool WasUpdated;
}
- private class TrailDrawNodeSharedData
- {
- public VertexBuffer VertexBuffer;
- }
-
private class TrailDrawNode : DrawNode
{
- public Shader Shader;
+ public IShader Shader;
public Texture Texture;
public float Time;
- public TrailDrawNodeSharedData Shared;
public readonly TrailPart[] Parts = new TrailPart[max_sprites];
public Vector2 Size;
+ private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw);
+
public TrailDrawNode()
{
for (int i = 0; i < max_sprites; i++)
@@ -194,9 +188,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override void Draw(Action vertexAction)
{
- if (Shared.VertexBuffer == null)
- Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw);
-
Shader.GetUniform("g_FadeClock").UpdateValue(ref Time);
int updateStart = -1, updateEnd = 0;
@@ -218,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
DrawColourInfo.Colour,
null,
- v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
+ v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex
{
Position = v.Position,
TexturePosition = v.TexturePosition,
@@ -230,24 +221,31 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
else if (updateStart != -1)
{
- Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
+ vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
updateStart = -1;
}
}
// Update all remaining vertices that have been changed.
if (updateStart != -1)
- Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
+ vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
base.Draw(vertexAction);
Shader.Bind();
Texture.TextureGL.Bind();
- Shared.VertexBuffer.Draw();
+ vertexBuffer.Draw();
Shader.Unbind();
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ vertexBuffer.Dispose();
+ }
}
[StructLayout(LayoutKind.Sequential)]
@@ -255,10 +253,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 Position;
+
[VertexMember(4, VertexAttribPointerType.Float)]
public Color4 Colour;
+
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
+
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs
similarity index 94%
rename from osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
rename to osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs
index 3fef769174..8c6723f5be 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
- public class GameplayCursor : CursorContainer, IKeyBindingHandler
+ public class GameplayCursorContainer : CursorContainer, IKeyBindingHandler
{
protected override Drawable CreateCursor() => new OsuCursor();
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Container fadeContainer;
- public GameplayCursor()
+ public GameplayCursorContainer()
{
InternalChild = fadeContainer = new Container
{
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public OsuCursor()
{
Origin = Anchor.Centre;
- Size = new Vector2(42);
+ Size = new Vector2(28);
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config, IBindableBeatmap beatmap)
+ private void load(OsuConfigManager config, IBindable beatmap)
{
InternalChild = expandTarget = new Container
{
@@ -183,13 +183,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
};
this.beatmap.BindTo(beatmap);
- this.beatmap.ValueChanged += v => calculateScale();
+ this.beatmap.ValueChanged += _ => calculateScale();
cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
- cursorScale.ValueChanged += v => calculateScale();
+ cursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
- autoCursorScale.ValueChanged += v => calculateScale();
+ autoCursorScale.ValueChanged += _ => calculateScale();
calculateScale();
}
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
float scale = (float)cursorScale.Value;
- if (autoCursorScale && beatmap.Value != null)
+ if (autoCursorScale.Value && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
@@ -213,6 +213,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public void Expand()
{
if (!cursorExpand) return;
+
expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad);
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
similarity index 76%
rename from osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
rename to osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index 86e5f8467d..b632e0fb05 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -2,25 +2,26 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Scoring;
-using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.UI
{
- public class OsuRulesetContainer : RulesetContainer
+ public class DrawableOsuRuleset : DrawableRuleset
{
- public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
+
+ public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
@@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI
protected override Playfield CreatePlayfield() => new OsuPlayfield();
- public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
+ protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
public override DrawableHitObject GetVisualRepresentation(OsuHitObject h)
{
@@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.UI
return null;
}
- protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
public override double GameplayStartTime
{
@@ -56,7 +57,5 @@ namespace osu.Game.Rulesets.Osu.UI
return first.StartTime - first.TimePreempt;
}
}
-
- protected override CursorContainer CreateCursor() => new GameplayCursor();
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index c4097ccb46..51733c3c01 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -10,18 +10,26 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI;
using System.Linq;
+using osu.Framework.Graphics.Cursor;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Osu.UI.Cursor;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuPlayfield : Playfield
{
- private readonly Container approachCircles;
+ private readonly ApproachCircleProxyContainer approachCircles;
private readonly JudgementContainer judgementLayer;
private readonly ConnectionRenderer connectionLayer;
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
+ private readonly PlayfieldAdjustmentContainer adjustmentContainer;
+
+ protected override Container CursorTargetContainer => adjustmentContainer;
+
+ protected override CursorContainer CreateCursor() => new GameplayCursorContainer();
+
public OsuPlayfield()
{
Anchor = Anchor.Centre;
@@ -29,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI
Size = new Vector2(0.75f);
- InternalChild = new PlayfieldAdjustmentContainer
+ InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@@ -45,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.UI
Depth = 1,
},
HitObjectContainer,
- approachCircles = new Container
+ approachCircles = new ApproachCircleProxyContainer
{
RelativeSizeAxes = Axes.Both,
Depth = -1,
@@ -58,13 +66,26 @@ namespace osu.Game.Rulesets.Osu.UI
{
h.OnNewResult += onNewResult;
- var c = h as IDrawableHitObjectWithProxiedApproach;
- if (c != null)
- approachCircles.Add(c.ProxiedLayer.CreateProxy());
+ if (h is IDrawableHitObjectWithProxiedApproach c)
+ {
+ var original = c.ProxiedLayer;
+
+ // Hitobjects only have lifetimes set on LoadComplete. For nested hitobjects (e.g. SliderHeads), this only happens when the parenting slider becomes visible.
+ // This delegation is required to make sure that the approach circles for those not-yet-loaded objects aren't added prematurely.
+ original.OnLoadComplete += addApproachCircleProxy;
+ }
base.Add(h);
}
+ private void addApproachCircleProxy(Drawable d)
+ {
+ var proxy = d.CreateProxy();
+ proxy.LifetimeStart = d.LifetimeStart;
+ proxy.LifetimeEnd = d.LifetimeEnd;
+ approachCircles.Add(proxy);
+ }
+
public override void PostProcess()
{
connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType();
@@ -72,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
- if (!judgedObject.DisplayResult || !DisplayJudgements)
+ if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject)
@@ -86,5 +107,10 @@ namespace osu.Game.Rulesets.Osu.UI
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
+
+ private class ApproachCircleProxyContainer : LifetimeManagementContainer
+ {
+ public void Add(Drawable approachCircleProxy) => AddInternal(approachCircleProxy);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
similarity index 64%
rename from osu.Game.Rulesets.Osu/UI/OsuSettings.cs
rename to osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
index 3620145ced..ce3432c73d 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
@@ -3,34 +3,36 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets.Osu.Configuration;
namespace osu.Game.Rulesets.Osu.UI
{
- public class OsuSettings : RulesetSettingsSubsection
+ public class OsuSettingsSubsection : RulesetSettingsSubsection
{
protected override string Header => "osu!";
- public OsuSettings(Ruleset ruleset)
+ public OsuSettingsSubsection(Ruleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
+ private void load()
{
+ var config = (OsuRulesetConfigManager)Config;
+
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = "Snaking in sliders",
- Bindable = config.GetBindable(OsuSetting.SnakingInSliders)
+ Bindable = config.GetBindable(OsuRulesetSetting.SnakingInSliders)
},
new SettingsCheckbox
{
LabelText = "Snaking out sliders",
- Bindable = config.GetBindable(OsuSetting.SnakingOutSliders)
+ Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders)
},
};
}
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..567220f316
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Taiko.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..6613e9e2b4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Rulesets.Taiko.Tests.iOS
+{
+ public class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..5fe822946a
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Rulesets.Taiko.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Rulesets-Taiko-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
new file mode 100644
index 0000000000..d2817b743c
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
@@ -0,0 +1,45 @@
+
+
+
+
+ Debug
+ iPhoneSimulator
+ {7E408809-66AC-49D1-AF4D-98834F9B979A}
+ Exe
+ osu.Game.Rulesets.Taiko.Tests
+ osu.Game.Rulesets.Taiko.Tests.iOS
+
+
+
+
+
+
+ libbass.a
+ PreserveNewest
+
+
+ libbass_fx.a
+ PreserveNewest
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}
+ osu.Game.Rulesets.Taiko
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs
new file mode 100644
index 0000000000..e7b6d8615b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Taiko.Difficulty;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TaikoDifficultyCalculatorTest : DifficultyCalculatorTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
+
+ [TestCase(2.9811338051242915d, "diffcalc-test")]
+ [TestCase(2.9811338051242915d, "diffcalc-test-strong")]
+ public void Test(double expected, string name)
+ => base.Test(expected, name);
+
+ protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap);
+
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
index 2c02649102..369cdd49d2 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override double TimePerAction => default_duration * 2;
private readonly Random rng = new Random(1337);
- private TaikoRulesetContainer rulesetContainer;
+ private DrawableTaikoRuleset drawableRuleset;
private Container playfieldContainer;
[BackgroundDependencyLoader]
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Ruleset = new TaikoRuleset().RulesetInfo
},
ControlPointInfo = controlPointInfo
- });
+ }, Clock);
Add(playfieldContainer = new Container
{
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 768,
- Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) }
+ Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) }
});
}
@@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
}
private void addStrongHitJudgement(bool kiai)
@@ -154,33 +154,33 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
- ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
- ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
{
- BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay };
+ BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay };
- rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
+ drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
}
private void addSwell(double duration = default_duration)
{
var swell = new Swell
{
- StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
+ StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
Duration = duration,
};
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- rulesetContainer.Playfield.Add(new DrawableSwell(swell));
+ drawableRuleset.Playfield.Add(new DrawableSwell(swell));
}
private void addDrumRoll(bool strong, double duration = default_duration)
@@ -190,40 +190,40 @@ namespace osu.Game.Rulesets.Taiko.Tests
var d = new DrumRoll
{
- StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
+ StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong,
Duration = duration,
};
d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- rulesetContainer.Playfield.Add(new DrawableDrumRoll(d));
+ drawableRuleset.Playfield.Add(new DrawableDrumRoll(d));
}
private void addCentreHit(bool strong)
{
Hit h = new Hit
{
- StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
+ StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- rulesetContainer.Playfield.Add(new DrawableCentreHit(h));
+ drawableRuleset.Playfield.Add(new DrawableCentreHit(h));
}
private void addRimHit(bool strong)
{
Hit h = new Hit
{
- StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
+ StartTime = drawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- rulesetContainer.Playfield.Add(new DrawableRimHit(h));
+ drawableRuleset.Playfield.Add(new DrawableRimHit(h));
}
private class TestStrongNestedHit : DrawableStrongNestedHit
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index fade054382..72ce6c947b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
new file mode 100644
index 0000000000..24345275c1
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
+{
+ public class TaikoDifficultyHitObject : DifficultyHitObject
+ {
+ public readonly bool HasTypeChange;
+
+ public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
+ : base(hitObject, lastObject, clockRate)
+ {
+ HasTypeChange = lastObject is RimHit != hitObject is RimHit;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs
new file mode 100644
index 0000000000..c6fe273b50
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs
@@ -0,0 +1,95 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
+{
+ public class Strain : Skill
+ {
+ private const double rhythm_change_base_threshold = 0.2;
+ private const double rhythm_change_base = 2.0;
+
+ protected override double SkillMultiplier => 1;
+ protected override double StrainDecayBase => 0.3;
+
+ private ColourSwitch lastColourSwitch = ColourSwitch.None;
+
+ private int sameColourCount = 1;
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ double addition = 1;
+
+ // We get an extra addition if we are not a slider or spinner
+ if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)
+ {
+ if (hasColourChange(current))
+ addition += 0.75;
+
+ if (hasRhythmChange(current))
+ addition += 1;
+ }
+ else
+ {
+ lastColourSwitch = ColourSwitch.None;
+ sameColourCount = 1;
+ }
+
+ double additionFactor = 1;
+
+ // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50
+ if (current.DeltaTime < 50)
+ additionFactor = 0.4 + 0.6 * current.DeltaTime / 50;
+
+ return additionFactor * addition;
+ }
+
+ private bool hasRhythmChange(DifficultyHitObject current)
+ {
+ // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time.
+ if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0)
+ return false;
+
+ double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime);
+
+ if (timeElapsedRatio >= 8)
+ return false;
+
+ double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
+
+ return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold;
+ }
+
+ private bool hasColourChange(DifficultyHitObject current)
+ {
+ var taikoCurrent = (TaikoDifficultyHitObject)current;
+
+ if (!taikoCurrent.HasTypeChange)
+ {
+ sameColourCount++;
+ return false;
+ }
+
+ var oldColourSwitch = lastColourSwitch;
+ var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd;
+
+ lastColourSwitch = newColourSwitch;
+ sameColourCount = 1;
+
+ // We only want a bonus if the parity of the color switch changes
+ return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch;
+ }
+
+ private enum ColourSwitch
+ {
+ None,
+ Even,
+ Odd
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index 3770f9601a..75d3807bba 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public double GreatHitWindow;
public int MaxCombo;
-
- public TaikoDifficultyAttributes(Mod[] mods, double starRating)
- : base(mods, starRating)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 16cebb0d96..685ad9949b 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -1,137 +1,51 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
- internal class TaikoDifficultyCalculator : DifficultyCalculator
+ public class TaikoDifficultyCalculator : DifficultyCalculator
{
private const double star_scaling_factor = 0.04125;
- ///
- /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
- /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
- /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
- ///
- private const double strain_step = 400;
-
- ///
- /// The weighting of each strain value decays to this number * it's previous value
- ///
- private const double decay_weight = 0.9;
-
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
- if (!beatmap.HitObjects.Any())
- return new TaikoDifficultyAttributes(mods, 0);
+ if (beatmap.HitObjects.Count == 0)
+ return new TaikoDifficultyAttributes { Mods = mods };
- var difficultyHitObjects = new List();
-
- foreach (var hitObject in beatmap.HitObjects)
- difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
-
- // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
- difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
-
- if (!calculateStrainValues(difficultyHitObjects, timeRate))
- return new DifficultyAttributes(mods, 0);
-
- double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
-
- return new TaikoDifficultyAttributes(mods, starRating)
+ return new TaikoDifficultyAttributes
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
- MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
+ StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
+ Mods = mods,
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
};
}
- private bool calculateStrainValues(List objects, double timeRate)
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
- // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
- using (var hitObjectsEnumerator = objects.GetEnumerator())
- {
- if (!hitObjectsEnumerator.MoveNext()) return false;
-
- TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current;
-
- // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
- while (hitObjectsEnumerator.MoveNext())
- {
- var next = hitObjectsEnumerator.Current;
- next?.CalculateStrains(current, timeRate);
- current = next;
- }
-
- return true;
- }
+ for (int i = 1; i < beatmap.HitObjects.Count; i++)
+ yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
}
- private double calculateDifficulty(List objects, double timeRate)
- {
- double actualStrainStep = strain_step * timeRate;
-
- // Find the highest strain value within each strain step
- List highestStrains = new List();
- double intervalEndTime = actualStrainStep;
- double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
-
- TaikoHitObjectDifficulty previousHitObject = null;
- foreach (var hitObject in objects)
- {
- // While we are beyond the current interval push the currently available maximum to our strain list
- while (hitObject.BaseHitObject.StartTime > intervalEndTime)
- {
- highestStrains.Add(maximumStrain);
-
- // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
- // until the beginning of the next interval.
- if (previousHitObject == null)
- {
- maximumStrain = 0;
- }
- else
- {
- double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
- maximumStrain = previousHitObject.Strain * decay;
- }
-
- // Go to the next time interval
- intervalEndTime += actualStrainStep;
- }
-
- // Obtain maximum strain
- maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
-
- previousHitObject = hitObject;
- }
-
- // Build the weighted sum over the highest strains for each interval
- double difficulty = 0;
- double weight = 1;
- highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
-
- foreach (double strain in highestStrains)
- {
- difficulty += weight * strain;
- weight *= decay_weight;
- }
-
- return difficulty;
- }
+ protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() };
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs
index 4e5491da9c..5b890b3d03 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModAutoplay : ModAutoplay
{
- protected override Score CreateReplayScore(Beatmap beatmap) => new Score
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
Replay = new TaikoAutoGenerator(beatmap).Generate(),
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index c7e6771b80..b7db3307ad 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
@@ -21,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods
private TaikoPlayfield playfield;
- public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- playfield = (TaikoPlayfield)rulesetContainer.Playfield;
- base.ApplyToRulesetContainer(rulesetContainer);
+ playfield = (TaikoPlayfield)drawableRuleset.Playfield;
+ base.ApplyToDrawableRuleset(drawableRuleset);
}
private class TaikoFlashlight : Flashlight
@@ -48,9 +49,9 @@ namespace osu.Game.Rulesets.Taiko.Mods
return default_flashlight_size;
}
- protected override void OnComboChange(int newCombo)
+ protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index d6a598fbec..9211eccc40 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -229,6 +229,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Ensure alternating centre and rim hits
if (lastWasCentre == isCentre)
return false;
+
lastWasCentre = isCentre;
UpdateResult(true);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 38bf877040..5f755c7cc3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected void ProxyContent()
{
if (isProxied) return;
+
isProxied = true;
nonProxiedContent.Remove(Content);
@@ -62,6 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected void UnproxyContent()
{
if (!isProxied) return;
+
isProxied = false;
proxiedContent.Remove(Content);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 5369499dbc..53dbe5d08e 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
///
public override Color4 AccentColour
{
- get { return base.AccentColour; }
+ get => base.AccentColour;
set
{
base.AccentColour = value;
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
///
public override bool KiaiMode
{
- get { return base.KiaiMode; }
+ get => base.KiaiMode;
set
{
base.KiaiMode = value;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index dd6a1a5021..773e3ae907 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -11,26 +11,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
{
private Color4 accentColour;
+
///
/// The colour of the inner circle and outer glows.
///
public virtual Color4 AccentColour
{
- get { return accentColour; }
- set { accentColour = value; }
+ get => accentColour;
+ set => accentColour = value;
}
private bool kiaiMode;
+
///
/// Whether Kiai mode effects are enabled for this circle piece.
///
public virtual bool KiaiMode
{
- get { return kiaiMode; }
- set
- {
- kiaiMode = value;
- }
+ get => kiaiMode;
+ set => kiaiMode = value;
}
public TaikoPiece()
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs
index d625047d29..83cf7a64ec 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs
@@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
private const float tick_size = 0.35f;
private bool filled;
+
public bool Filled
{
- get { return filled; }
+ get => filled;
set
{
filled = value;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index 9d511daae4..befa728570 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -19,7 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
///
public int RequiredHits = 10;
- public override bool IsStrong { set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); }
+ public override bool IsStrong
+ {
+ set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
+ }
protected override void CreateNestedHitObjects()
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs
deleted file mode 100644
index 46dd7aa87f..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- internal class TaikoHitObjectDifficulty
- {
- ///
- /// Factor by how much individual / overall strain decays per second.
- ///
- ///
- /// These values are results of tweaking a lot and taking into account general feedback.
- ///
- internal const double DECAY_BASE = 0.30;
-
- private const double type_change_bonus = 0.75;
- private const double rhythm_change_bonus = 1.0;
- private const double rhythm_change_base_threshold = 0.2;
- private const double rhythm_change_base = 2.0;
-
- internal TaikoHitObject BaseHitObject;
-
- ///
- /// Measures note density in a way
- ///
- internal double Strain = 1;
-
- private double timeElapsed;
- private int sameTypeSince = 1;
-
- private bool isRim => BaseHitObject is RimHit;
-
- public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject)
- {
- BaseHitObject = baseHitObject;
- }
-
- internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate)
- {
- // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
- // See Taiko feedback thread.
- timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
- double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
-
- double addition = 1;
-
- // Only if we are no slider or spinner we get an extra addition
- if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit
- && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past
- {
- addition += typeChangeAddition(previousHitObject);
- addition += rhythmChangeAddition(previousHitObject);
- }
-
- double additionFactor = 1.0;
- // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50
- if (timeElapsed < 50.0)
- additionFactor = 0.4 + 0.6 * timeElapsed / 50.0;
-
- Strain = previousHitObject.Strain * decay + addition * additionFactor;
- }
-
- private TypeSwitch lastTypeSwitchEven = TypeSwitch.None;
- private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject)
- {
- // If we don't have the same hit type, trigger a type change!
- if (previousHitObject.isRim != isRim)
- {
- lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd;
-
- // We only want a bonus if the parity of the type switch changes!
- switch (previousHitObject.lastTypeSwitchEven)
- {
- case TypeSwitch.Even:
- if (lastTypeSwitchEven == TypeSwitch.Odd)
- return type_change_bonus;
- break;
- case TypeSwitch.Odd:
- if (lastTypeSwitchEven == TypeSwitch.Even)
- return type_change_bonus;
- break;
- }
- }
- // No type change? Increment counter and keep track of last type switch
- else
- {
- lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven;
- sameTypeSince = previousHitObject.sameTypeSince + 1;
- }
-
- return 0;
- }
-
- private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject)
- {
- // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time.
- if (timeElapsed == 0 || previousHitObject.timeElapsed == 0)
- return 0;
-
- double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed);
-
- if (timeElapsedRatio >= 8)
- return 0;
-
- double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
-
- if (isWithinChangeThreshold(difference))
- return rhythm_change_bonus;
-
- return 0;
- }
-
- private bool isWithinChangeThreshold(double value)
- {
- return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold;
- }
-
- private enum TypeSwitch
- {
- None,
- Even,
- Odd
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
index ca6da65107..81f15fb293 100644
--- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
@@ -9,3 +9,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.iOS")]
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 14a6f98480..01ba53e07b 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -9,14 +9,17 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Taiko.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Replays
{
- public class TaikoAutoGenerator : AutoGenerator
+ public class TaikoAutoGenerator : AutoGenerator
{
+ public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap;
+
private const double swell_hit_speed = 50;
- public TaikoAutoGenerator(Beatmap beatmap)
+ public TaikoAutoGenerator(IBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay();
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu
new file mode 100644
index 0000000000..33510eceb7
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu
@@ -0,0 +1,257 @@
+osu file format v14
+
+[General]
+Mode: 1
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+62500,-500,4,2,1,50,0,0
+71000,-100,4,2,1,50,0,0
+
+[HitObjects]
+// Same as diffcalc-test with finishers on every note
+142,122,0,5,4,0:0:0:0:
+142,122,125,1,4,0:0:0:0:
+142,122,250,1,4,0:0:0:0:
+142,122,375,1,4,0:0:0:0:
+142,122,500,1,4,0:0:0:0:
+142,122,625,1,4,0:0:0:0:
+142,122,750,1,4,0:0:0:0:
+142,122,875,1,4,0:0:0:0:
+142,122,1000,1,4,0:0:0:0:
+142,122,1125,1,4,0:0:0:0:
+142,122,1250,1,4,0:0:0:0:
+142,122,1375,1,4,0:0:0:0:
+142,122,1500,1,4,0:0:0:0:
+119,106,2500,1,6,0:0:0:0:
+119,106,2625,1,6,0:0:0:0:
+119,106,2750,1,6,0:0:0:0:
+119,106,2875,1,6,0:0:0:0:
+119,106,3000,1,6,0:0:0:0:
+119,106,3125,1,6,0:0:0:0:
+119,106,3250,1,6,0:0:0:0:
+119,106,3375,1,6,0:0:0:0:
+119,106,3500,1,6,0:0:0:0:
+119,106,3625,1,6,0:0:0:0:
+119,106,3750,1,6,0:0:0:0:
+119,106,3875,1,6,0:0:0:0:
+119,106,4000,1,6,0:0:0:0:
+136,90,5000,1,4,0:0:0:0:
+136,90,5125,1,6,0:0:0:0:
+136,90,5250,1,4,0:0:0:0:
+136,90,5375,1,6,0:0:0:0:
+136,90,5500,1,4,0:0:0:0:
+136,90,5625,1,6,0:0:0:0:
+136,90,5750,1,4,0:0:0:0:
+136,90,5875,1,6,0:0:0:0:
+136,90,6000,1,4,0:0:0:0:
+136,90,6125,1,6,0:0:0:0:
+136,90,6250,1,4,0:0:0:0:
+136,90,6375,1,6,0:0:0:0:
+136,90,6500,1,4,0:0:0:0:
+86,113,7500,1,4,0:0:0:0:
+86,113,7625,1,4,0:0:0:0:
+86,113,7750,1,6,0:0:0:0:
+86,113,7875,1,6,0:0:0:0:
+86,113,8000,1,4,0:0:0:0:
+86,113,8125,1,4,0:0:0:0:
+86,113,8250,1,6,0:0:0:0:
+86,113,8375,1,6,0:0:0:0:
+86,113,8500,1,4,0:0:0:0:
+86,113,8625,1,4,0:0:0:0:
+86,113,8750,1,6,0:0:0:0:
+86,113,8875,1,6,0:0:0:0:
+86,113,9000,1,4,0:0:0:0:
+146,90,10000,1,4,0:0:0:0:
+146,90,10125,1,4,0:0:0:0:
+146,90,10250,1,4,0:0:0:0:
+146,90,10375,1,6,0:0:0:0:
+146,90,10500,1,6,0:0:0:0:
+146,90,10625,1,6,0:0:0:0:
+146,90,10750,1,4,0:0:0:0:
+146,90,10875,1,4,0:0:0:0:
+146,90,11000,1,4,0:0:0:0:
+146,90,11125,1,6,0:0:0:0:
+146,90,11250,1,6,0:0:0:0:
+146,90,11375,1,6,0:0:0:0:
+146,90,11500,1,4,0:0:0:0:
+146,90,11625,1,4,0:0:0:0:
+146,90,11750,1,4,0:0:0:0:
+146,90,11875,1,6,0:0:0:0:
+146,90,12000,1,6,0:0:0:0:
+146,90,12125,1,6,0:0:0:0:
+146,90,12250,1,4,0:0:0:0:
+146,90,12375,1,4,0:0:0:0:
+146,90,12500,1,4,0:0:0:0:
+69,99,13500,1,4,0:0:0:0:
+69,99,13625,1,4,0:0:0:0:
+69,99,13750,1,4,0:0:0:0:
+69,99,13875,1,6,0:0:0:0:
+69,99,14000,1,4,0:0:0:0:
+69,99,14125,1,4,0:0:0:0:
+69,99,14250,1,4,0:0:0:0:
+69,99,14375,1,6,0:0:0:0:
+69,99,14500,1,4,0:0:0:0:
+69,99,14625,1,4,0:0:0:0:
+69,99,14750,1,4,0:0:0:0:
+69,99,14875,1,6,0:0:0:0:
+69,99,15000,1,4,0:0:0:0:
+69,99,15125,1,4,0:0:0:0:
+69,99,15250,1,4,0:0:0:0:
+69,99,15375,1,6,0:0:0:0:
+69,99,15500,1,4,0:0:0:0:
+83,89,16500,1,4,0:0:0:0:
+83,89,16625,1,6,0:0:0:0:
+83,89,16750,1,6,0:0:0:0:
+83,89,16875,1,4,0:0:0:0:
+83,89,17000,1,4,0:0:0:0:
+83,89,17125,1,4,0:0:0:0:
+83,89,17250,1,6,0:0:0:0:
+83,89,17375,1,6,0:0:0:0:
+83,89,17500,1,6,0:0:0:0:
+83,89,17625,1,6,0:0:0:0:
+83,89,17750,1,4,0:0:0:0:
+83,89,17875,1,4,0:0:0:0:
+83,89,18000,1,4,0:0:0:0:
+83,89,18125,1,4,0:0:0:0:
+83,89,18250,1,4,0:0:0:0:
+83,89,18375,1,6,0:0:0:0:
+83,89,18500,1,6,0:0:0:0:
+83,89,18625,1,6,0:0:0:0:
+83,89,18750,1,6,0:0:0:0:
+83,89,18875,1,4,0:0:0:0:
+83,89,19000,1,4,0:0:0:0:
+83,89,19125,1,4,0:0:0:0:
+83,89,19250,1,4,0:0:0:0:
+83,89,19375,1,6,0:0:0:0:
+83,89,19500,1,6,0:0:0:0:
+83,89,19625,1,4,0:0:0:0:
+84,122,20500,1,4,0:0:0:0:
+84,122,20625,2,4,L|217:123,1,120
+84,122,21125,1,4,0:0:0:0:
+84,122,21250,2,4,L|217:123,1,120
+84,122,21750,1,4,0:0:0:0:
+84,122,21875,2,4,L|217:123,1,120
+84,122,22375,1,4,0:0:0:0:
+84,122,22500,2,4,L|217:123,1,120
+84,122,23000,1,4,0:0:0:0:
+84,122,23125,2,4,L|217:123,1,120
+99,106,24500,1,4,0:0:0:0:
+99,106,24625,1,4,0:0:0:0:
+99,106,24750,2,4,L|194:107,1,80
+99,106,25125,1,4,0:0:0:0:
+99,106,25250,1,4,0:0:0:0:
+99,106,25375,2,4,L|194:107,1,80
+99,106,25750,1,4,0:0:0:0:
+99,106,25875,1,4,0:0:0:0:
+99,106,26000,2,4,L|194:107,1,80
+99,106,26375,1,4,0:0:0:0:
+99,106,26500,1,4,0:0:0:0:
+99,106,26625,2,4,L|194:107,1,80
+99,106,27000,1,4,0:0:0:0:
+99,106,27125,1,4,0:0:0:0:
+99,106,27250,2,4,L|194:107,1,80
+121,103,28500,1,4,0:0:0:0:
+121,103,28625,1,4,0:0:0:0:
+121,103,28750,1,4,0:0:0:0:
+121,103,28875,2,4,L|190:103,1,40
+121,103,29125,1,4,0:0:0:0:
+121,103,29250,1,4,0:0:0:0:
+121,103,29375,1,4,0:0:0:0:
+121,103,29500,2,4,L|190:103,1,40
+121,103,29750,1,4,0:0:0:0:
+121,103,29875,1,4,0:0:0:0:
+121,103,30000,1,4,0:0:0:0:
+121,103,30125,2,4,L|190:103,1,40
+121,103,30375,1,4,0:0:0:0:
+121,103,30500,1,4,0:0:0:0:
+121,103,30625,1,4,0:0:0:0:
+121,103,30750,2,4,L|190:103,1,40
+121,103,31000,1,4,0:0:0:0:
+121,103,31125,1,4,0:0:0:0:
+121,103,31250,1,4,0:0:0:0:
+121,103,31375,2,4,L|190:103,1,40
+121,103,32500,1,4,0:0:0:0:
+121,103,32625,1,6,0:0:0:0:
+121,103,32750,1,4,0:0:0:0:
+121,103,32875,2,4,L|190:103,1,40
+121,103,33125,1,4,0:0:0:0:
+121,103,33250,1,6,0:0:0:0:
+121,103,33375,1,4,0:0:0:0:
+121,103,33500,2,4,L|190:103,1,40
+121,103,33750,1,4,0:0:0:0:
+121,103,33875,1,6,0:0:0:0:
+121,103,34000,1,4,0:0:0:0:
+121,103,34125,2,4,L|190:103,1,40
+121,103,34375,1,4,0:0:0:0:
+121,103,34500,1,6,0:0:0:0:
+121,103,34625,1,4,0:0:0:0:
+121,103,34750,2,4,L|190:103,1,40
+121,103,35000,1,4,0:0:0:0:
+121,103,35125,1,6,0:0:0:0:
+121,103,35250,1,4,0:0:0:0:
+121,103,35375,2,4,L|190:103,1,40
+121,103,36500,1,4,0:0:0:0:
+121,103,36625,1,4,0:0:0:0:
+121,103,36750,1,6,0:0:0:0:
+121,103,36875,2,4,L|190:103,1,40
+121,103,37125,1,4,0:0:0:0:
+121,103,37250,1,4,0:0:0:0:
+121,103,37375,1,6,0:0:0:0:
+121,103,37500,2,4,L|190:103,1,40
+121,103,37750,1,4,0:0:0:0:
+121,103,37875,1,4,0:0:0:0:
+121,103,38000,1,6,0:0:0:0:
+121,103,38125,2,4,L|190:103,1,40
+121,103,38375,1,4,0:0:0:0:
+121,103,38500,1,4,0:0:0:0:
+121,103,38625,1,6,0:0:0:0:
+121,103,38750,2,4,L|190:103,1,40
+121,103,39000,1,4,0:0:0:0:
+121,103,39125,1,4,0:0:0:0:
+121,103,39250,1,6,0:0:0:0:
+121,103,39375,2,4,L|190:103,1,40
+107,106,40500,1,4,0:0:0:0:
+107,106,40625,1,4,0:0:0:0:
+107,106,40750,1,6,0:0:0:0:
+107,106,40875,1,6,0:0:0:0:
+46,112,41000,2,4,L|214:112,1,160
+107,106,41625,1,4,0:0:0:0:
+107,106,41750,1,4,0:0:0:0:
+107,106,41875,1,6,0:0:0:0:
+107,106,42000,1,6,0:0:0:0:
+46,112,42125,2,4,L|214:112,1,160
+107,106,42750,1,4,0:0:0:0:
+107,106,42875,1,4,0:0:0:0:
+107,106,43000,1,6,0:0:0:0:
+107,106,43125,1,6,0:0:0:0:
+46,112,43250,2,4,L|214:112,1,160
+107,106,43875,1,4,0:0:0:0:
+107,106,44000,1,4,0:0:0:0:
+107,106,44125,1,6,0:0:0:0:
+107,106,44250,1,6,0:0:0:0:
+46,112,44375,2,4,L|214:112,1,160
+107,106,45000,1,4,0:0:0:0:
+107,106,45125,1,4,0:0:0:0:
+107,106,45250,1,6,0:0:0:0:
+107,106,45375,1,6,0:0:0:0:
+46,112,45500,2,4,L|214:112,1,160
+256,192,47000,12,4,47500,0:0:0:0:
+256,192,47625,12,4,48000,0:0:0:0:
+256,192,48125,12,4,48500,0:0:0:0:
+256,192,48625,12,4,49000,0:0:0:0:
+256,192,50000,12,4,50500,0:0:0:0:
+183,143,50625,5,4,0:0:0:0:
+256,192,50750,12,4,51250,0:0:0:0:
+114,106,51375,5,4,0:0:0:0:
+256,192,51625,12,4,52125,0:0:0:0:
+154,143,52250,5,4,0:0:0:0:
+256,192,52375,12,4,52875,0:0:0:0:
+116,111,53000,5,4,0:0:0:0:
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu
new file mode 100644
index 0000000000..15326162ea
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu
@@ -0,0 +1,285 @@
+osu file format v14
+
+[General]
+Mode: 1
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+62500,-500,4,2,1,50,0,0
+71000,-100,4,2,1,50,0,0
+
+[HitObjects]
+// dd, spaced 1/4 beat apart
+142,122,0,5,0,0:0:0:0:
+142,122,125,1,0,0:0:0:0:
+142,122,250,1,0,0:0:0:0:
+142,122,375,1,0,0:0:0:0:
+142,122,500,1,0,0:0:0:0:
+142,122,625,1,0,0:0:0:0:
+142,122,750,1,0,0:0:0:0:
+142,122,875,1,0,0:0:0:0:
+142,122,1000,1,0,0:0:0:0:
+142,122,1125,1,0,0:0:0:0:
+142,122,1250,1,0,0:0:0:0:
+142,122,1375,1,0,0:0:0:0:
+142,122,1500,1,0,0:0:0:0:
+
+// kk, spaced 1/4 beat apart
+119,106,2500,1,2,0:0:0:0:
+119,106,2625,1,2,0:0:0:0:
+119,106,2750,1,2,0:0:0:0:
+119,106,2875,1,2,0:0:0:0:
+119,106,3000,1,2,0:0:0:0:
+119,106,3125,1,2,0:0:0:0:
+119,106,3250,1,2,0:0:0:0:
+119,106,3375,1,2,0:0:0:0:
+119,106,3500,1,2,0:0:0:0:
+119,106,3625,1,2,0:0:0:0:
+119,106,3750,1,2,0:0:0:0:
+119,106,3875,1,2,0:0:0:0:
+119,106,4000,1,2,0:0:0:0:
+
+// dk, spaced 1/4 beat apart
+136,90,5000,1,0,0:0:0:0:
+136,90,5125,1,2,0:0:0:0:
+136,90,5250,1,0,0:0:0:0:
+136,90,5375,1,2,0:0:0:0:
+136,90,5500,1,0,0:0:0:0:
+136,90,5625,1,2,0:0:0:0:
+136,90,5750,1,0,0:0:0:0:
+136,90,5875,1,2,0:0:0:0:
+136,90,6000,1,0,0:0:0:0:
+136,90,6125,1,2,0:0:0:0:
+136,90,6250,1,0,0:0:0:0:
+136,90,6375,1,2,0:0:0:0:
+136,90,6500,1,0,0:0:0:0:
+
+// ddkk, spaced 1/4 beat apart
+86,113,7500,1,0,0:0:0:0:
+86,113,7625,1,0,0:0:0:0:
+86,113,7750,1,2,0:0:0:0:
+86,113,7875,1,2,0:0:0:0:
+86,113,8000,1,0,0:0:0:0:
+86,113,8125,1,0,0:0:0:0:
+86,113,8250,1,2,0:0:0:0:
+86,113,8375,1,2,0:0:0:0:
+86,113,8500,1,0,0:0:0:0:
+86,113,8625,1,0,0:0:0:0:
+86,113,8750,1,2,0:0:0:0:
+86,113,8875,1,2,0:0:0:0:
+86,113,9000,1,0,0:0:0:0:
+
+// dddkkk, spaced 1/4 beat apart
+146,90,10000,1,0,0:0:0:0:
+146,90,10125,1,0,0:0:0:0:
+146,90,10250,1,0,0:0:0:0:
+146,90,10375,1,2,0:0:0:0:
+146,90,10500,1,2,0:0:0:0:
+146,90,10625,1,2,0:0:0:0:
+146,90,10750,1,0,0:0:0:0:
+146,90,10875,1,0,0:0:0:0:
+146,90,11000,1,0,0:0:0:0:
+146,90,11125,1,2,0:0:0:0:
+146,90,11250,1,2,0:0:0:0:
+146,90,11375,1,2,0:0:0:0:
+146,90,11500,1,0,0:0:0:0:
+146,90,11625,1,0,0:0:0:0:
+146,90,11750,1,0,0:0:0:0:
+146,90,11875,1,2,0:0:0:0:
+146,90,12000,1,2,0:0:0:0:
+146,90,12125,1,2,0:0:0:0:
+146,90,12250,1,0,0:0:0:0:
+146,90,12375,1,0,0:0:0:0:
+146,90,12500,1,0,0:0:0:0:
+
+// dddk, spaced 1/4 beat apart
+69,99,13500,1,0,0:0:0:0:
+69,99,13625,1,0,0:0:0:0:
+69,99,13750,1,0,0:0:0:0:
+69,99,13875,1,2,0:0:0:0:
+69,99,14000,1,0,0:0:0:0:
+69,99,14125,1,0,0:0:0:0:
+69,99,14250,1,0,0:0:0:0:
+69,99,14375,1,2,0:0:0:0:
+69,99,14500,1,0,0:0:0:0:
+69,99,14625,1,0,0:0:0:0:
+69,99,14750,1,0,0:0:0:0:
+69,99,14875,1,2,0:0:0:0:
+69,99,15000,1,0,0:0:0:0:
+69,99,15125,1,0,0:0:0:0:
+69,99,15250,1,0,0:0:0:0:
+69,99,15375,1,2,0:0:0:0:
+69,99,15500,1,0,0:0:0:0:
+
+// arbitrary pattern, spaced 1/4 beat apart
+83,89,16500,1,0,0:0:0:0:
+83,89,16625,1,2,0:0:0:0:
+83,89,16750,1,2,0:0:0:0:
+83,89,16875,1,0,0:0:0:0:
+83,89,17000,1,0,0:0:0:0:
+83,89,17125,1,0,0:0:0:0:
+83,89,17250,1,2,0:0:0:0:
+83,89,17375,1,2,0:0:0:0:
+83,89,17500,1,2,0:0:0:0:
+83,89,17625,1,2,0:0:0:0:
+83,89,17750,1,0,0:0:0:0:
+83,89,17875,1,0,0:0:0:0:
+83,89,18000,1,0,0:0:0:0:
+83,89,18125,1,0,0:0:0:0:
+83,89,18250,1,0,0:0:0:0:
+83,89,18375,1,2,0:0:0:0:
+83,89,18500,1,2,0:0:0:0:
+83,89,18625,1,2,0:0:0:0:
+83,89,18750,1,2,0:0:0:0:
+83,89,18875,1,0,0:0:0:0:
+83,89,19000,1,0,0:0:0:0:
+83,89,19125,1,0,0:0:0:0:
+83,89,19250,1,0,0:0:0:0:
+83,89,19375,1,2,0:0:0:0:
+83,89,19500,1,2,0:0:0:0:
+83,89,19625,1,0,0:0:0:0:
+
+// d-slider pattern, spaced 1/4 beat apart
+84,122,20500,1,0,0:0:0:0:
+84,122,20625,2,0,L|217:123,1,120
+84,122,21125,1,0,0:0:0:0:
+84,122,21250,2,0,L|217:123,1,120
+84,122,21750,1,0,0:0:0:0:
+84,122,21875,2,0,L|217:123,1,120
+84,122,22375,1,0,0:0:0:0:
+84,122,22500,2,0,L|217:123,1,120
+84,122,23000,1,0,0:0:0:0:
+84,122,23125,2,0,L|217:123,1,120
+
+// dd-slider pattern, spaced 1/4 beat apart
+99,106,24500,1,0,0:0:0:0:
+99,106,24625,1,0,0:0:0:0:
+99,106,24750,2,0,L|194:107,1,80
+99,106,25125,1,0,0:0:0:0:
+99,106,25250,1,0,0:0:0:0:
+99,106,25375,2,0,L|194:107,1,80
+99,106,25750,1,0,0:0:0:0:
+99,106,25875,1,0,0:0:0:0:
+99,106,26000,2,0,L|194:107,1,80
+99,106,26375,1,0,0:0:0:0:
+99,106,26500,1,0,0:0:0:0:
+99,106,26625,2,0,L|194:107,1,80
+99,106,27000,1,0,0:0:0:0:
+99,106,27125,1,0,0:0:0:0:
+99,106,27250,2,0,L|194:107,1,80
+
+// ddd-slider pattern, spaced 1/4 beat apart
+121,103,28500,1,0,0:0:0:0:
+121,103,28625,1,0,0:0:0:0:
+121,103,28750,1,0,0:0:0:0:
+121,103,28875,2,0,L|190:103,1,40
+121,103,29125,1,0,0:0:0:0:
+121,103,29250,1,0,0:0:0:0:
+121,103,29375,1,0,0:0:0:0:
+121,103,29500,2,0,L|190:103,1,40
+121,103,29750,1,0,0:0:0:0:
+121,103,29875,1,0,0:0:0:0:
+121,103,30000,1,0,0:0:0:0:
+121,103,30125,2,0,L|190:103,1,40
+121,103,30375,1,0,0:0:0:0:
+121,103,30500,1,0,0:0:0:0:
+121,103,30625,1,0,0:0:0:0:
+121,103,30750,2,0,L|190:103,1,40
+121,103,31000,1,0,0:0:0:0:
+121,103,31125,1,0,0:0:0:0:
+121,103,31250,1,0,0:0:0:0:
+121,103,31375,2,0,L|190:103,1,40
+
+// dkd-slider pattern, spaced 1/4 beat apart
+121,103,32500,1,0,0:0:0:0:
+121,103,32625,1,2,0:0:0:0:
+121,103,32750,1,0,0:0:0:0:
+121,103,32875,2,0,L|190:103,1,40
+121,103,33125,1,0,0:0:0:0:
+121,103,33250,1,2,0:0:0:0:
+121,103,33375,1,0,0:0:0:0:
+121,103,33500,2,0,L|190:103,1,40
+121,103,33750,1,0,0:0:0:0:
+121,103,33875,1,2,0:0:0:0:
+121,103,34000,1,0,0:0:0:0:
+121,103,34125,2,0,L|190:103,1,40
+121,103,34375,1,0,0:0:0:0:
+121,103,34500,1,2,0:0:0:0:
+121,103,34625,1,0,0:0:0:0:
+121,103,34750,2,0,L|190:103,1,40
+121,103,35000,1,0,0:0:0:0:
+121,103,35125,1,2,0:0:0:0:
+121,103,35250,1,0,0:0:0:0:
+121,103,35375,2,0,L|190:103,1,40
+
+//ddk-slider pattern, spaced 1/4 beat apart
+121,103,36500,1,0,0:0:0:0:
+121,103,36625,1,0,0:0:0:0:
+121,103,36750,1,2,0:0:0:0:
+121,103,36875,2,0,L|190:103,1,40
+121,103,37125,1,0,0:0:0:0:
+121,103,37250,1,0,0:0:0:0:
+121,103,37375,1,2,0:0:0:0:
+121,103,37500,2,0,L|190:103,1,40
+121,103,37750,1,0,0:0:0:0:
+121,103,37875,1,0,0:0:0:0:
+121,103,38000,1,2,0:0:0:0:
+121,103,38125,2,0,L|190:103,1,40
+121,103,38375,1,0,0:0:0:0:
+121,103,38500,1,0,0:0:0:0:
+121,103,38625,1,2,0:0:0:0:
+121,103,38750,2,0,L|190:103,1,40
+121,103,39000,1,0,0:0:0:0:
+121,103,39125,1,0,0:0:0:0:
+121,103,39250,1,2,0:0:0:0:
+121,103,39375,2,0,L|190:103,1,40
+
+//ddkk-slider pattern, spaced 1/4 beat apart
+107,106,40500,1,0,0:0:0:0:
+107,106,40625,1,0,0:0:0:0:
+107,106,40750,1,2,0:0:0:0:
+107,106,40875,1,2,0:0:0:0:
+46,112,41000,2,0,L|214:112,1,160
+107,106,41625,1,0,0:0:0:0:
+107,106,41750,1,0,0:0:0:0:
+107,106,41875,1,2,0:0:0:0:
+107,106,42000,1,2,0:0:0:0:
+46,112,42125,2,0,L|214:112,1,160
+107,106,42750,1,0,0:0:0:0:
+107,106,42875,1,0,0:0:0:0:
+107,106,43000,1,2,0:0:0:0:
+107,106,43125,1,2,0:0:0:0:
+46,112,43250,2,0,L|214:112,1,160
+107,106,43875,1,0,0:0:0:0:
+107,106,44000,1,0,0:0:0:0:
+107,106,44125,1,2,0:0:0:0:
+107,106,44250,1,2,0:0:0:0:
+46,112,44375,2,0,L|214:112,1,160
+107,106,45000,1,0,0:0:0:0:
+107,106,45125,1,0,0:0:0:0:
+107,106,45250,1,2,0:0:0:0:
+107,106,45375,1,2,0:0:0:0:
+46,112,45500,2,0,L|214:112,1,160
+
+// spinner-spinner pattern, spaced 1/4 beat apart
+256,192,47000,12,0,47500,0:0:0:0:
+256,192,47625,12,0,48000,0:0:0:0:
+256,192,48125,12,0,48500,0:0:0:0:
+256,192,48625,12,0,49000,0:0:0:0:
+
+// spinner-d pattern, spaced 1/4 beat apart
+256,192,50000,12,0,50500,0:0:0:0:
+183,143,50625,5,0,0:0:0:0:
+256,192,50750,12,0,51250,0:0:0:0:
+114,106,51375,5,0,0:0:0:0:
+256,192,51625,12,0,52125,0:0:0:0:
+154,143,52250,5,0,0:0:0:0:
+256,192,52375,12,0,52875,0:0:0:0:
+116,111,53000,5,0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index f7638a8122..442cca49f8 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring
///
private double hpMissMultiplier;
- public TaikoScoreProcessor(RulesetContainer rulesetContainer)
- : base(rulesetContainer)
+ public TaikoScoreProcessor(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
{
}
@@ -67,6 +67,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring
Health.Value = 0;
}
- protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
+ public override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
index 2aae0b23b7..058bec5111 100644
--- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs
@@ -19,10 +19,13 @@ namespace osu.Game.Rulesets.Taiko
{
[Description("Left (rim)")]
LeftRim,
+
[Description("Left (centre)")]
LeftCentre,
+
[Description("Right (centre)")]
RightCentre,
+
[Description("Right (rim)")]
RightRim
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 7851a2f919..3e94775eb6 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
+using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
@@ -99,6 +100,11 @@ namespace osu.Game.Rulesets.Taiko
new MultiMod(new TaikoModAutoplay(), new ModCinema()),
new TaikoModRelax(),
};
+ case ModType.Fun:
+ return new Mod[]
+ {
+ new MultiMod(new ModWindUp(), new ModWindDown())
+ };
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
index 90841f11f5..943adaed4b 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
@@ -39,12 +39,10 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
- protected override void LoadComplete()
+ protected override void ApplyHitAnimations()
{
- if (Result.IsHit)
- this.MoveToY(-100, 500);
-
- base.LoadComplete();
+ this.MoveToY(-100, 500);
+ base.ApplyHitAnimations();
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
similarity index 92%
rename from osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 7a73f4bd2a..899b91863e 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -20,13 +20,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
{
- public class TaikoRulesetContainer : ScrollingRulesetContainer
+ public class DrawableTaikoRuleset : DrawableScrollingRuleset
{
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
protected override bool UserScrollSpeedAdjustment => false;
- public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
Direction.Value = ScrollingDirection.Left;
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
- public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
+ protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 8dc9a2ca37..35b941b52b 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public class TaikoPlayfield : ScrollingPlayfield
{
///
- /// Default height of a when inside a .
+ /// Default height of a when inside a .
///
public const float DEFAULT_HEIGHT = 178;
@@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Taiko.UI
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
- if (!DisplayJudgements)
+ if (!DisplayJudgements.Value)
return;
if (!judgedObject.DisplayResult)
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index 002d6a7e8d..656ebcc7c2 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Game.Tests.iOS/AppDelegate.cs b/osu.Game.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..1e703e0c0a
--- /dev/null
+++ b/osu.Game.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+
+namespace osu.Game.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..a23fe4e129
--- /dev/null
+++ b/osu.Game.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Tests.iOS
+{
+ public class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Tests.iOS/Entitlements.plist b/osu.Game.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..98a4223116
--- /dev/null
+++ b/osu.Game.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
new file mode 100644
index 0000000000..ea5ab699f3
--- /dev/null
+++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
@@ -0,0 +1,60 @@
+
+
+
+
+ Debug
+ iPhoneSimulator
+ Exe
+ {65FF8E19-6934-469B-B690-23C6D6E56A17}
+ osu.Game.Tests
+ osu.Game.Tests.iOS
+
+
+
+
+
+
+ libbass.a
+ PreserveNewest
+
+
+ libbass_fx.a
+ PreserveNewest
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}
+ osu.Game.Rulesets.Osu
+
+
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}
+ osu.Game.Rulesets.Catch
+
+
+ {48F4582B-7687-4621-9CBE-5C24197CB536}
+ osu.Game.Rulesets.Mania
+
+
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}
+ osu.Game.Rulesets.Taiko
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 1221212f94..02dff6993d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapVersion()
{
- using (var resStream = Resource.OpenResource("beatmap-version.osu"))
+ using (var resStream = TestResources.OpenResource("beatmap-version.osu"))
using (var stream = new StreamReader(resStream))
{
var decoder = Decoder.GetDecoder(stream);
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapGeneral()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapEditor()
{
var decoder = new LegacyBeatmapDecoder();
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmapInfo = decoder.Decode(stream).BeatmapInfo;
@@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapMetadata()
{
var decoder = new LegacyBeatmapDecoder();
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -119,7 +119,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapDifficulty()
{
var decoder = new LegacyBeatmapDecoder();
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty;
@@ -137,7 +137,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapEvents()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -155,7 +155,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapTimingPoints()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -190,7 +190,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapColours()
{
var decoder = new LegacySkinDecoder();
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
@@ -215,7 +215,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapComboOffsetsOsu()
{
var decoder = new LegacyBeatmapDecoder();
- using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu"))
+ using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapComboOffsetsCatch()
{
var decoder = new LegacyBeatmapDecoder();
- using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu"))
+ using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.Decode(stream);
@@ -259,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapHitObjects()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
+ using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@@ -286,7 +286,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeControlPointCustomSampleBank()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("controlpoint-custom-samplebank.osu"))
+ using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@@ -307,7 +307,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeHitObjectCustomSampleBank()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("hitobject-custom-samplebank.osu"))
+ using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@@ -324,7 +324,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeHitObjectFileSamples()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("hitobject-file-samples.osu"))
+ using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@@ -333,6 +333,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
+ Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
@@ -342,7 +343,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeSliderSamples()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("slider-samples.osu"))
+ using (var resStream = TestResources.OpenResource("slider-samples.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@@ -385,7 +386,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeHitObjectNullAdditionBank()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
- using (var resStream = Resource.OpenResource("hitobject-no-addition-bank.osu"))
+ using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 5049349999..136d1de930 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeStoryboardEvents()
{
var decoder = new LegacyStoryboardDecoder();
- using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
+ using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
using (var stream = new StreamReader(resStream))
{
var storyboard = decoder.Decode(stream);
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeVariableWithSuffix()
{
var decoder = new LegacyStoryboardDecoder();
- using (var resStream = Resource.OpenResource("variable-with-suffix.osb"))
+ using (var resStream = TestResources.OpenResource("variable-with-suffix.osb"))
using (var stream = new StreamReader(resStream))
{
var storyboard = decoder.Decode(stream);
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 44291a6805..a867ddebae 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -113,6 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestCase(normal)]
[TestCase(marathon)]
+ [Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")]
// Currently fails:
// [TestCase(with_sb)]
public void TestParity(string beatmap)
@@ -146,7 +147,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
/// The after being decoded by an .
private Beatmap decode(string filename, out Beatmap jsonDecoded)
{
- using (var stream = Resource.OpenResource(filename))
+ using (var stream = TestResources.OpenResource(filename))
using (var sr = new StreamReader(stream))
{
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs
new file mode 100644
index 0000000000..b3863bcf44
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Globalization;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Formats;
+
+namespace osu.Game.Tests.Beatmaps.Formats
+{
+ [TestFixture]
+ public class ParsingTest
+ {
+ [Test]
+ public void TestNaNHandling() => allThrow("NaN");
+
+ [Test]
+ public void TestBadStringHandling() => allThrow("Random string 123");
+
+ [TestCase(Parsing.MAX_PARSE_VALUE)]
+ [TestCase(-1)]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(-Parsing.MAX_PARSE_VALUE)]
+ [TestCase(10, 10)]
+ [TestCase(-10, 10)]
+ public void TestValidRanges(double input, double limit = Parsing.MAX_PARSE_VALUE)
+ {
+ Assert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input);
+ Assert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input);
+ Assert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input);
+ }
+
+ [TestCase(double.PositiveInfinity)]
+ [TestCase(double.NegativeInfinity)]
+ [TestCase(999999999999)]
+ [TestCase(Parsing.MAX_PARSE_VALUE * 1.1)]
+ [TestCase(-Parsing.MAX_PARSE_VALUE * 1.1)]
+ [TestCase(11, 10)]
+ [TestCase(-11, 10)]
+ public void TestOutOfRangeHandling(double input, double limit = Parsing.MAX_PARSE_VALUE)
+ => allThrow(input.ToString(CultureInfo.InvariantCulture), limit);
+
+ private void allThrow(string input, double limit = Parsing.MAX_PARSE_VALUE)
+ where T : Exception
+ {
+ Assert.Throws(getIntParseException(input) ?? typeof(T), () => Parsing.ParseInt(input, (int)limit));
+ Assert.Throws(() => Parsing.ParseFloat(input, (float)limit));
+ Assert.Throws(() => Parsing.ParseDouble(input, limit));
+ }
+
+ ///
+ /// may not be able to parse some inputs.
+ /// In this case we expect to receive the raw parsing exception.
+ ///
+ /// The input attempting to be parsed.
+ /// The type of exception thrown by . Null if no exception is thrown.
+ private Type getIntParseException(string input)
+ {
+ try
+ {
+ var _ = int.Parse(input);
+ }
+ catch (Exception e)
+ {
+ return e.GetType();
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index a159659d35..f020c2a805 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using osu.Game.Tests.Resources;
using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Beatmaps.IO
@@ -19,8 +20,6 @@ namespace osu.Game.Tests.Beatmaps.IO
[TestFixture]
public class ImportBeatmapTest
{
- public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
-
[Test]
public void TestImportWhenClosed()
{
@@ -102,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.IO
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
- manager.ItemAdded += (_, __, ___) => fireCount++;
+ manager.ItemAdded += (_, __) => fireCount++;
manager.ItemRemoved += _ => fireCount++;
var imported = LoadOszIntoOsu(osu);
@@ -114,7 +113,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual(0, fireCount -= 2);
- var breakTemp = createTemporaryBeatmap();
+ var breakTemp = TestResources.GetTestBeatmapForImport();
MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
@@ -208,6 +207,96 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var imported = LoadOszIntoOsu(osu);
+
+ if (set)
+ imported.OnlineBeatmapSetID = 1234;
+ else
+ imported.Beatmaps.First().OnlineBeatmapID = 1234;
+
+ osu.Dependencies.Get().Update(imported);
+
+ deleteBeatmapSet(imported, osu);
+
+ var importedSecondTime = LoadOszIntoOsu(osu);
+
+ // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
+ Assert.IsTrue(imported.ID != importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestImportWithDuplicateBeatmapIDs()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var metadata = new BeatmapMetadata
+ {
+ Artist = "SomeArtist",
+ AuthorString = "SomeAuthor"
+ };
+
+ var difficulty = new BeatmapDifficulty();
+
+ var toImport = new BeatmapSetInfo
+ {
+ OnlineBeatmapSetID = 1,
+ Metadata = metadata,
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ OnlineBeatmapID = 2,
+ Metadata = metadata,
+ BaseDifficulty = difficulty
+ },
+ new BeatmapInfo
+ {
+ OnlineBeatmapID = 2,
+ Metadata = metadata,
+ Status = BeatmapSetOnlineStatus.Loved,
+ BaseDifficulty = difficulty
+ }
+ }
+ };
+
+ var manager = osu.Dependencies.Get();
+
+ var imported = manager.Import(toImport);
+
+ Assert.NotNull(imported);
+ Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
+ Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
[Test]
[NonParallelizable]
[Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")]
@@ -223,7 +312,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
- var temp = createTemporaryBeatmap();
+ var temp = TestResources.GetTestBeatmapForImport();
var importer = new ArchiveImportIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(10000))
@@ -248,7 +337,7 @@ namespace osu.Game.Tests.Beatmaps.IO
try
{
var osu = loadOsu(host);
- var temp = createTemporaryBeatmap();
+ var temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
osu.Dependencies.Get().Import(temp);
ensureLoaded(osu);
@@ -262,17 +351,9 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- private static string createTemporaryBeatmap()
- {
- var temp = Path.GetTempFileName() + ".osz";
- File.Copy(TEST_OSZ_PATH, temp, true);
- Assert.IsTrue(File.Exists(temp));
- return temp;
- }
-
public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
{
- var temp = path ?? createTemporaryBeatmap();
+ var temp = path ?? TestResources.GetTestBeatmapForImport();
var manager = osu.Dependencies.Get();
diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
index aa95e7390f..17197aff27 100644
--- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestReadBeatmaps()
{
- using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
+ using (var osz = TestResources.GetTestBeatmapStream())
{
var reader = new ZipArchiveReader(osz);
string[] expected =
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestReadMetadata()
{
- using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
+ using (var osz = TestResources.GetTestBeatmapStream())
{
var reader = new ZipArchiveReader(osz);
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestReadFile()
{
- using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
+ using (var osz = TestResources.GetTestBeatmapStream())
{
var reader = new ZipArchiveReader(osz);
using (var stream = new StreamReader(
diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
index 795c2244a2..760a033aff 100644
--- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
+++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
@@ -2,9 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Tests.NonVisual
@@ -15,7 +18,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestNoMods()
{
- var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(1, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -24,7 +27,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestSingleMod()
{
- var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(2, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -34,7 +37,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDoubleMod()
{
- var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(4, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -49,7 +52,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestIncompatibleMods()
{
- var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(3, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -60,7 +63,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDoubleIncompatibleMods()
{
- var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(8, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -83,7 +86,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestIncompatibleThroughBaseType()
{
- var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
+ var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(3, combinations.Length);
Assert.IsTrue(combinations[0] is ModNoMod);
@@ -136,9 +139,9 @@ namespace osu.Game.Tests.NonVisual
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
}
- private class TestDifficultyCalculator : DifficultyCalculator
+ private class TestLegacyDifficultyCalculator : DifficultyCalculator
{
- public TestDifficultyCalculator(params Mod[] mods)
+ public TestLegacyDifficultyCalculator(params Mod[] mods)
: base(null, null)
{
DifficultyAdjustmentMods = mods;
@@ -146,7 +149,20 @@ namespace osu.Game.Tests.NonVisual
protected override Mod[] DifficultyAdjustmentMods { get; }
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap)
+ {
+ throw new NotImplementedException();
+ }
}
}
}
diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
new file mode 100644
index 0000000000..1c78b63499
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
@@ -0,0 +1,115 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Rulesets.Difficulty.Utils;
+
+namespace osu.Game.Tests.NonVisual
+{
+ [TestFixture]
+ public class LimitedCapacityStackTest
+ {
+ private const int capacity = 3;
+
+ private LimitedCapacityStack stack;
+
+ [SetUp]
+ public void Setup()
+ {
+ stack = new LimitedCapacityStack(capacity);
+ }
+
+ [Test]
+ public void TestEmptyStack()
+ {
+ Assert.AreEqual(0, stack.Count);
+
+ Assert.Throws(() =>
+ {
+ int unused = stack[0];
+ });
+
+ int count = 0;
+ foreach (var unused in stack)
+ count++;
+
+ Assert.AreEqual(0, count);
+ }
+
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(3)]
+ public void TestInRangeElements(int count)
+ {
+ // e.g. 0 -> 1 -> 2
+ for (int i = 0; i < count; i++)
+ stack.Push(i);
+
+ Assert.AreEqual(count, stack.Count);
+
+ // e.g. 2 -> 1 -> 0 (reverse order)
+ for (int i = 0; i < stack.Count; i++)
+ Assert.AreEqual(count - 1 - i, stack[i]);
+
+ // e.g. indices 3, 4, 5, 6 (out of range)
+ for (int i = stack.Count; i < stack.Count + capacity; i++)
+ {
+ Assert.Throws(() =>
+ {
+ int unused = stack[i];
+ });
+ }
+ }
+
+ [TestCase(4)]
+ [TestCase(5)]
+ [TestCase(6)]
+ public void TestOverflowElements(int count)
+ {
+ // e.g. 0 -> 1 -> 2 -> 3
+ for (int i = 0; i < count; i++)
+ stack.Push(i);
+
+ Assert.AreEqual(capacity, stack.Count);
+
+ // e.g. 3 -> 2 -> 1 (reverse order)
+ for (int i = 0; i < stack.Count; i++)
+ Assert.AreEqual(count - 1 - i, stack[i]);
+
+ // e.g. indices 3, 4, 5, 6 (out of range)
+ for (int i = stack.Count; i < stack.Count + capacity; i++)
+ {
+ Assert.Throws(() =>
+ {
+ int unused = stack[i];
+ });
+ }
+ }
+
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(3)]
+ [TestCase(4)]
+ [TestCase(5)]
+ [TestCase(6)]
+ public void TestEnumerator(int count)
+ {
+ // e.g. 0 -> 1 -> 2 -> 3
+ for (int i = 0; i < count; i++)
+ stack.Push(i);
+
+ int enumeratorCount = 0;
+ int expectedValue = count - 1;
+
+ foreach (var item in stack)
+ {
+ Assert.AreEqual(expectedValue, item);
+ enumeratorCount++;
+ expectedValue--;
+ }
+
+ Assert.AreEqual(stack.Count, enumeratorCount);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Resource.cs b/osu.Game.Tests/Resources/Resource.cs
deleted file mode 100644
index 5405436422..0000000000
--- a/osu.Game.Tests/Resources/Resource.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace osu.Game.Tests.Resources
-{
- public static class Resource
- {
- public static Stream OpenResource(string name)
- {
- var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
-
- return Assembly.GetExecutingAssembly().GetManifestResourceStream($@"osu.Game.Tests.Resources.{name}") ??
- Assembly.LoadFrom(Path.Combine(localPath, @"osu.Game.Resources.dll")).GetManifestResourceStream($@"osu.Game.Resources.{name}");
- }
- }
-}
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
new file mode 100644
index 0000000000..9cb85a63bf
--- /dev/null
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using NUnit.Framework;
+using osu.Framework.IO.Stores;
+
+namespace osu.Game.Tests.Resources
+{
+ public static class TestResources
+ {
+ public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
+
+ public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz");
+
+ public static string GetTestBeatmapForImport()
+ {
+ var temp = Path.GetTempFileName() + ".osz";
+
+ using (var stream = GetTestBeatmapStream())
+ using (var newFile = File.Create(temp))
+ stream.CopyTo(newFile);
+
+ Assert.IsTrue(File.Exists(temp));
+ return temp;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/hitobject-file-samples.osu b/osu.Game.Tests/Resources/hitobject-file-samples.osu
index 588672e2d9..7c69f259b8 100644
--- a/osu.Game.Tests/Resources/hitobject-file-samples.osu
+++ b/osu.Game.Tests/Resources/hitobject-file-samples.osu
@@ -13,4 +13,4 @@ SampleSet: Normal
255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0:
-256,191,3576,1,0,0:0:0:0:hit_1.wav
+256,191,3576,1,0,0:0:0:70:hit_1.wav
diff --git a/osu.Game.Tests/Resources/skin-empty.ini b/osu.Game.Tests/Resources/skin-empty.ini
new file mode 100644
index 0000000000..b6c319fe3c
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin-empty.ini
@@ -0,0 +1,2 @@
+[General]
+Name: test skin
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini
new file mode 100644
index 0000000000..0e5737b4ea
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin.ini
@@ -0,0 +1,8 @@
+[General]
+Name: test skin
+
+[Colours]
+Combo1 : 142,199,255
+Combo2 : 255,128,128
+Combo3 : 128,255,255
+Combo7 : 100,100,100,100
\ No newline at end of file
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index f5ebe313dd..e39f18c3cd 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -16,14 +15,13 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using osu.Game.Tests.Resources;
using osu.Game.Users;
namespace osu.Game.Tests.Scores.IO
{
public class ImportScoreTest
{
- public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
-
[Test]
public void TestBasicImport()
{
@@ -132,21 +130,13 @@ namespace osu.Game.Tests.Scores.IO
return scoreManager.GetAllUsableScores().First();
}
- private string createTemporaryBeatmap()
- {
- var temp = Path.GetTempFileName() + ".osz";
- File.Copy(TEST_OSZ_PATH, temp, true);
- Assert.IsTrue(File.Exists(temp));
- return temp;
- }
-
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
- var beatmapFile = createTemporaryBeatmap();
+ var beatmapFile = TestResources.GetTestBeatmapForImport();
var beatmapManager = osu.Dependencies.Get();
beatmapManager.Import(beatmapFile);
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
new file mode 100644
index 0000000000..2a97519e21
--- /dev/null
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using NUnit.Framework;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class LegacySkinDecoderTest
+ {
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestDecodeSkinColours(bool hasColours)
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var comboColors = decoder.Decode(stream).ComboColours;
+
+ List expectedColors;
+ if (hasColours)
+ expectedColors = new List
+ {
+ new Color4(142, 199, 255, 255),
+ new Color4(255, 128, 128, 255),
+ new Color4(128, 255, 255, 255),
+ new Color4(100, 100, 100, 100),
+ };
+ else
+ expectedColors = new DefaultSkin().Configuration.ComboColours;
+
+ Assert.AreEqual(expectedColors.Count, comboColors.Count);
+ for (int i = 0; i < expectedColors.Count; i++)
+ Assert.AreEqual(expectedColors[i], comboColors[i]);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs
index 543a43b439..24380645d1 100644
--- a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs
@@ -3,9 +3,13 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.AccountCreation;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
@@ -21,12 +25,32 @@ namespace osu.Game.Tests.Visual
typeof(AccountCreationScreen),
};
+ [Cached(typeof(IAPIProvider))]
+ private DummyAPIAccess api = new DummyAPIAccess();
+
public TestCaseAccountCreationOverlay()
{
- var accountCreation = new AccountCreationOverlay();
- Child = accountCreation;
+ Container userPanelArea;
+ AccountCreationOverlay accountCreation;
- accountCreation.State = Visibility.Visible;
+ Children = new Drawable[]
+ {
+ api,
+ accountCreation = new AccountCreationOverlay(),
+ userPanelArea = new Container
+ {
+ Padding = new MarginPadding(10),
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ },
+ };
+
+ api.Logout();
+ api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
+
+ AddStep("show", () => accountCreation.State = Visibility.Visible);
+ AddStep("logout", () => api.Logout());
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs
deleted file mode 100644
index a5decaa9fb..0000000000
--- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using NUnit.Framework;
-
-namespace osu.Game.Tests.Visual
-{
- [TestFixture]
- public class TestCaseAllPlayers : TestCasePlayer
- {
- }
-}
diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs
index 3f3d62377e..4d6a0ae7b8 100644
--- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.ComponentModel;
using System.Linq;
using osu.Game.Rulesets;
@@ -11,7 +10,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
[Description("Player instantiated with an autoplay mod.")]
- public class TestCaseAutoplay : TestCasePlayer
+ public class TestCaseAutoplay : AllPlayersTestCase
{
protected override Player CreatePlayer(Ruleset ruleset)
{
@@ -24,11 +23,10 @@ namespace osu.Game.Tests.Visual
};
}
- protected override void AddCheckSteps(Func player)
+ protected override void AddCheckSteps()
{
- base.AddCheckSteps(player);
- AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore > 0, "score above zero");
- AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys");
+ AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
+ AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
}
private class ScoreAccessiblePlayer : Player
diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs
new file mode 100644
index 0000000000..ba3a02a843
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs
@@ -0,0 +1,436 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Platform;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Scoring;
+using osu.Game.Screens;
+using osu.Game.Screens.Backgrounds;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.PlayerSettings;
+using osu.Game.Screens.Select;
+using osu.Game.Tests.Resources;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseBackgroundScreenBeatmap : ManualInputManagerTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ScreenWithBeatmapBackground),
+ typeof(PlayerLoader),
+ typeof(Player),
+ typeof(UserDimContainer),
+ typeof(OsuScreen)
+ };
+
+ private DummySongSelect songSelect;
+ private TestPlayerLoader playerLoader;
+ private TestPlayer player;
+ private DatabaseContextFactory factory;
+ private BeatmapManager manager;
+ private RulesetStore rulesets;
+
+ private ScreenStackCacheContainer screenStackContainer;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host)
+ {
+ factory = new DatabaseContextFactory(LocalStorage);
+ factory.ResetDatabase();
+
+ using (var usage = factory.Get())
+ usage.Migrate();
+
+ factory.ResetDatabase();
+
+ using (var usage = factory.Get())
+ usage.Migrate();
+
+ Dependencies.Cache(rulesets = new RulesetStore(factory));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default));
+ Dependencies.Cache(new OsuConfigManager(LocalStorage));
+
+ manager.Import(TestResources.GetTestBeatmapForImport());
+
+ Beatmap.SetDefault();
+ }
+
+ [SetUp]
+ public virtual void SetUp() => Schedule(() =>
+ {
+ Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both };
+ screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect());
+ });
+
+ ///
+ /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
+ ///
+ [Test]
+ public void PlayerLoaderSettingsHoverTest()
+ {
+ setupUserSettings();
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer())));
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ AddStep("Trigger background preview", () =>
+ {
+ InputManager.MoveMouseTo(playerLoader.ScreenPos);
+ InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
+ });
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ ///
+ /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
+ /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
+ /// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
+ ///
+ [Test]
+ public void PlayerLoaderTransitionTest()
+ {
+ performFullSetup();
+ AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
+ ///
+ [Test]
+ public void StoryboardBackgroundVisibilityTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Storyboard Enabled", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ waitForDim();
+ AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible());
+ AddStep("Storyboard Disabled", () =>
+ {
+ player.ReplacesBackground.Value = false;
+ player.StoryboardEnabled.Value = false;
+ });
+ waitForDim();
+ AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && player.IsStoryboardInvisible());
+ }
+
+ ///
+ /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
+ ///
+ [Test]
+ public void StoryboardTransitionTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
+ }
+
+ ///
+ /// Check if the is properly accepting user-defined visual changes at all.
+ ///
+ [Test]
+ public void DisableUserDimTest()
+ {
+ performFullSetup();
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
+ AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Check if the visual settings container retains dim and blur when pausing
+ ///
+ [Test]
+ public void PauseTest()
+ {
+ performFullSetup(true);
+ AddStep("Pause", () => player.Pause());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Unpause", () => player.Resume());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Check if the visual settings container removes user dim when suspending for
+ ///
+ [Test]
+ public void TransitionTest()
+ {
+ performFullSetup();
+ var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } });
+ AddStep("Transition to Results", () => player.Push(results));
+ AddUntilStep("Wait for results is current", results.IsCurrentScreen);
+ waitForDim();
+ AddAssert("Screen is undimmed, original background retained", () =>
+ songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if background gets undimmed and unblurred when leaving for
+ ///
+ [Test]
+ public void TransitionOutTest()
+ {
+ performFullSetup();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
+ ///
+ [Test]
+ public void ResumeFromPlayerTest()
+ {
+ performFullSetup();
+ AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
+ AddStep("Resume PlayerLoader", () => player.Restart());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ private void waitForDim() => AddWaitStep("Wait for dim", 5);
+
+ private void createFakeStoryboard() => AddStep("Create storyboard", () =>
+ {
+ player.StoryboardEnabled.Value = false;
+ player.ReplacesBackground.Value = false;
+ player.CurrentStoryboardContainer.Add(new SpriteText
+ {
+ Size = new Vector2(250, 50),
+ Alpha = 1,
+ Colour = Color4.Tomato,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "THIS IS A STORYBOARD",
+ });
+ });
+
+ private void performFullSetup(bool allowPause = false)
+ {
+ setupUserSettings();
+
+ AddStep("Start player loader", () =>
+ {
+ songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer
+ {
+ AllowPause = allowPause,
+ Ready = true,
+ }));
+ });
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ AddUntilStep("Wait for player to load", () => player.IsLoaded);
+ }
+
+ private void setupUserSettings()
+ {
+ AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddStep("Set default user settings", () =>
+ {
+ Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() });
+ songSelect.DimLevel.Value = 0.7f;
+ songSelect.BlurLevel.Value = 0.4f;
+ });
+ }
+
+ private class DummySongSelect : PlaySongSelect
+ {
+ protected override BackgroundScreen CreateBackground()
+ {
+ FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
+ DimEnabled.BindTo(background.EnableUserDim);
+ return background;
+ }
+
+ public readonly Bindable DimEnabled = new Bindable();
+ public readonly Bindable DimLevel = new Bindable();
+ public readonly Bindable BlurLevel = new Bindable();
+
+ public new BeatmapCarousel Carousel => base.Carousel;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.DimLevel, DimLevel);
+ config.BindWith(OsuSetting.BlurLevel, BlurLevel);
+ }
+
+ public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value);
+
+ public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
+
+ public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25);
+
+ public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
+
+ public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
+
+ public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ ///
+ /// Make sure every time a screen gets pushed, the background doesn't get replaced
+ ///
+ /// Whether or not the original background (The one created in DummySongSelect) is still the current background
+ public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
+ }
+
+ private class FadeAccessibleResults : SoloResults
+ {
+ public FadeAccessibleResults(ScoreInfo score)
+ : base(score)
+ {
+ }
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ }
+
+ private class TestPlayer : Player
+ {
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ protected override UserDimContainer CreateStoryboardContainer()
+ {
+ return new TestUserDimContainer(true)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 1,
+ EnableUserDim = { Value = true }
+ };
+ }
+
+ public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;
+
+ // Whether or not the player should be allowed to load.
+ public bool Ready;
+
+ public Bindable StoryboardEnabled;
+ public readonly Bindable ReplacesBackground = new Bindable();
+ public readonly Bindable IsPaused = new Bindable();
+
+ public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1;
+
+ public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ while (!Ready)
+ Thread.Sleep(1);
+ StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
+ ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
+ DrawableRuleset.IsPaused.BindTo(IsPaused);
+ }
+ }
+
+ private class ScreenStackCacheContainer : Container
+ {
+ [Cached]
+ private BackgroundScreenStack backgroundScreenStack;
+
+ public readonly ScreenStack ScreenStack;
+
+ public ScreenStackCacheContainer()
+ {
+ Add(backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both });
+ Add(ScreenStack = new ScreenStack { RelativeSizeAxes = Axes.Both });
+ }
+ }
+
+ private class TestPlayerLoader : PlayerLoader
+ {
+ public VisualSettings VisualSettingsPos => VisualSettings;
+ public BackgroundScreen ScreenPos => Background;
+
+ public TestPlayerLoader(Player player)
+ : base(() => player)
+ {
+ }
+
+ public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ }
+
+ private class FadeAccessibleBackground : BackgroundScreenBeatmap
+ {
+ protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both };
+
+ public Color4 CurrentColour => fadeContainer.CurrentColour;
+
+ public float CurrentAlpha => fadeContainer.CurrentAlpha;
+
+ public Vector2 CurrentBlur => Background.BlurSigma;
+
+ private TestUserDimContainer fadeContainer;
+
+ public FadeAccessibleBackground(WorkingBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+ }
+
+ private class TestUserDimContainer : UserDimContainer
+ {
+ public Color4 CurrentColour => DimContainer.Colour;
+ public float CurrentAlpha => DimContainer.Alpha;
+
+ public TestUserDimContainer(bool isStoryboard = false)
+ : base(isStoryboard)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
index 85ef5963a5..2fd8d467f6 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
@@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK.Graphics;
using osu.Framework.Lists;
+using osu.Game.Graphics;
namespace osu.Game.Tests.Visual
{
@@ -140,6 +141,7 @@ namespace osu.Game.Tests.Visual
}
private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints;
+
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
if (timingPoints[timingPoints.Count - 1] == current)
@@ -189,15 +191,15 @@ namespace osu.Game.Tests.Visual
public double Value
{
- set { valueText.Text = $"{value:G}"; }
+ set => valueText.Text = $"{value:G}";
}
public InfoString(string header)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
- Add(new OsuSpriteText { Text = header + @": ", TextSize = text_size });
- Add(valueText = new OsuSpriteText { TextSize = text_size });
+ Add(new OsuSpriteText { Text = header + @": ", Font = OsuFont.GetFont(size: text_size) });
+ Add(valueText = new OsuSpriteText { Font = OsuFont.GetFont(size: text_size) });
Margin = new MarginPadding(margin);
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
index e61bca9846..956d84618c 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
});
- AddUntilStep(() => changed, "Wait for load");
+ AddUntilStep("Wait for load", () => changed);
}
private void ensureRandomFetchSuccess() =>
@@ -147,9 +147,10 @@ namespace osu.Game.Tests.Visual
private bool selectedBeatmapVisible()
{
- var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected);
+ var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
if (currentlySelected == null)
return true;
+
return currentlySelected.Item.Visible;
}
@@ -166,8 +167,7 @@ namespace osu.Game.Tests.Visual
carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false);
carousel.Filter(new FilterCriteria(), false);
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
- }
- );
+ });
}
///
@@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual
checkSelected(3, 2);
AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria()));
- AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce");
+ AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(diff: false, count: set_count);
checkVisibleItemCount(diff: true, count: 3);
@@ -327,13 +327,13 @@ namespace osu.Game.Tests.Visual
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
checkSelected(1);
- AddUntilStep(() =>
+ AddUntilStep("Remove all", () =>
{
if (!carousel.BeatmapSets.Any()) return true;
carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last());
return false;
- }, "Remove all");
+ });
checkNoSelection();
}
@@ -522,6 +522,7 @@ namespace osu.Game.Tests.Visual
}
});
}
+
return toReturn;
}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs
index c23075a127..6cc3982f9c 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs
@@ -1,8 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
using osuTK;
@@ -12,14 +16,145 @@ namespace osu.Game.Tests.Visual
[System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
public class TestCaseBeatmapDetailArea : OsuTestCase
{
+ public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) };
+
public TestCaseBeatmapDetailArea()
{
- Add(new BeatmapDetailArea
+ BeatmapDetailArea detailsArea;
+ Add(detailsArea = new BeatmapDetailArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(550f, 450f),
});
+
+ AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap
+ {
+ BeatmapInfo =
+ {
+ Version = "All Metrics",
+ Metadata = new BeatmapMetadata
+ {
+ Source = "osu!lazer",
+ Tags = "this beatmap has all the metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 7,
+ DrainRate = 1,
+ OverallDifficulty = 5.7f,
+ ApproachRate = 3.5f,
+ },
+ StarDifficulty = 5.3f,
+ Metrics = new BeatmapMetrics
+ {
+ Ratings = Enumerable.Range(0, 11),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ },
+ }
+ }
+ );
+
+ AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap
+ {
+ BeatmapInfo =
+ {
+ Version = "All Metrics",
+ Metadata = new BeatmapMetadata
+ {
+ Tags = "this beatmap has all the metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 7,
+ DrainRate = 1,
+ OverallDifficulty = 5.7f,
+ ApproachRate = 3.5f,
+ },
+ StarDifficulty = 5.3f,
+ Metrics = new BeatmapMetrics
+ {
+ Ratings = Enumerable.Range(0, 11),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ },
+ }
+ });
+
+ AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap
+ {
+ BeatmapInfo =
+ {
+ Version = "Only Ratings",
+ Metadata = new BeatmapMetadata
+ {
+ Source = "osu!lazer",
+ Tags = "this beatmap has ratings metrics but not retries or fails",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 6,
+ DrainRate = 9,
+ OverallDifficulty = 6,
+ ApproachRate = 6,
+ },
+ StarDifficulty = 4.8f,
+ Metrics = new BeatmapMetrics
+ {
+ Ratings = Enumerable.Range(0, 11),
+ },
+ }
+ });
+
+ AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap
+ {
+ BeatmapInfo =
+ {
+ Version = "Only Retries and Fails",
+ Metadata = new BeatmapMetadata
+ {
+ Source = "osu!lazer",
+ Tags = "this beatmap has retries and fails but no ratings",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 3.7f,
+ DrainRate = 6,
+ OverallDifficulty = 6,
+ ApproachRate = 7,
+ },
+ StarDifficulty = 2.91f,
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ },
+ }
+ });
+
+ AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap
+ {
+ BeatmapInfo =
+ {
+ Version = "No Metrics",
+ Metadata = new BeatmapMetadata
+ {
+ Source = "osu!lazer",
+ Tags = "this beatmap has no metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 5,
+ DrainRate = 5,
+ OverallDifficulty = 5.5f,
+ ApproachRate = 6.5f,
+ },
+ StarDifficulty = 1.97f,
+ }
+ });
+
+ AddStep("null beatmap", () => detailsArea.Beatmap = null);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs
index bc4f89ac26..84af6453f5 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
Padding = new MarginPadding(150),
});
- AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo
+ AddStep("all metrics", () => details.Beatmap = new BeatmapInfo
{
Version = "All Metrics",
Metadata = new BeatmapMetadata
@@ -45,7 +45,30 @@ namespace osu.Game.Tests.Visual
},
});
- AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo
+ AddStep("all except source", () => details.Beatmap = new BeatmapInfo
+ {
+ Version = "All Metrics",
+ Metadata = new BeatmapMetadata
+ {
+ Tags = "this beatmap has all the metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 7,
+ DrainRate = 1,
+ OverallDifficulty = 5.7f,
+ ApproachRate = 3.5f,
+ },
+ StarDifficulty = 5.3f,
+ Metrics = new BeatmapMetrics
+ {
+ Ratings = Enumerable.Range(0, 11),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ },
+ });
+
+ AddStep("ratings", () => details.Beatmap = new BeatmapInfo
{
Version = "Only Ratings",
Metadata = new BeatmapMetadata
@@ -67,7 +90,7 @@ namespace osu.Game.Tests.Visual
},
});
- AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo
+ AddStep("fails retries", () => details.Beatmap = new BeatmapInfo
{
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
@@ -90,7 +113,7 @@ namespace osu.Game.Tests.Visual
},
});
- AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo
+ AddStep("no metrics", () => details.Beatmap = new BeatmapInfo
{
Version = "No Metrics",
Metadata = new BeatmapMetadata
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index d3056f0b13..0d77ac666b 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -50,17 +50,17 @@ namespace osu.Game.Tests.Visual
AddStep("show", () =>
{
infoWedge.State = Visibility.Visible;
- infoWedge.Beatmap = Beatmap;
+ infoWedge.Beatmap = Beatmap.Value;
});
// select part is redundant, but wait for load isn't
selectBeatmap(Beatmap.Value.Beatmap);
- AddWaitStep(3);
+ AddWaitStep("wait for select", 3);
AddStep("hide", () => { infoWedge.State = Visibility.Hidden; });
- AddWaitStep(3);
+ AddWaitStep("wait for hide", 3);
AddStep("show", () => { infoWedge.State = Visibility.Visible; });
@@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b);
});
- AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load");
+ AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore);
}
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs
index 321a38d087..bb55c0b1e8 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs
@@ -23,9 +23,6 @@ namespace osu.Game.Tests.Visual
[System.ComponentModel.Description("in BeatmapOverlay")]
public class TestCaseBeatmapScoresContainer : OsuTestCase
{
- private readonly IEnumerable scores;
- private readonly IEnumerable anotherScores;
- private readonly APIScoreInfo topScoreInfo;
private readonly Box background;
public TestCaseBeatmapScoresContainer()
@@ -47,15 +44,7 @@ namespace osu.Game.Tests.Visual
}
};
- AddStep("scores pack 1", () => scoresContainer.Scores = scores);
- AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores);
- AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo });
- AddStep("remove scores", () => scoresContainer.Scores = null);
- AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
- AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
- AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
-
- scores = new[]
+ IEnumerable scores = new[]
{
new APIScoreInfo
{
@@ -160,14 +149,15 @@ namespace osu.Game.Tests.Visual
Accuracy = 0.6543,
},
};
- foreach(var s in scores)
+
+ foreach (var s in scores)
{
s.Statistics.Add(HitResult.Great, RNG.Next(2000));
s.Statistics.Add(HitResult.Good, RNG.Next(2000));
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
- anotherScores = new[]
+ IEnumerable anotherScores = new[]
{
new APIScoreInfo
{
@@ -279,7 +269,7 @@ namespace osu.Game.Tests.Visual
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
- topScoreInfo = new APIScoreInfo
+ var topScoreInfo = new APIScoreInfo
{
User = new User
{
@@ -304,6 +294,14 @@ namespace osu.Game.Tests.Visual
topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000));
topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000));
topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000));
+
+ AddStep("scores pack 1", () => scoresContainer.Scores = scores);
+ AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores);
+ AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo });
+ AddStep("remove scores", () => scoresContainer.Scores = null);
+ AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
+ AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
+ AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
index 6e60eb3306..e90b5f5372 100644
--- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual
});
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
- channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString();
+ channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString();
AddStep("Add random private channel", addRandomPrivateChannel);
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);
@@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual
AddStep("set second channel", () => channelTabControl.Current.Value = channelTabControl.Items.Skip(1).First());
AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value);
- AddUntilStep(() =>
+ AddUntilStep("remove all channels", () =>
{
var first = channelTabControl.Items.First();
if (first.Name == "+")
@@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual
channelTabControl.RemoveChannel(first);
return false;
- }, "remove all channels");
+ });
AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value);
}
diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs
index f119e6dc24..ecab64ccf3 100644
--- a/osu.Game.Tests/Visual/TestCaseChatLink.cs
+++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs
@@ -13,10 +13,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Bindables;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
-using osu.Framework.Configuration;
namespace osu.Game.Tests.Visual
{
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual
var chatManager = new ChannelManager();
BindableList availableChannels = (BindableList)chatManager.AvailableChannels;
- availableChannels.Add(new Channel { Name = "#english"});
+ availableChannels.Add(new Channel { Name = "#english" });
availableChannels.Add(new Channel { Name = "#japanese" });
Dependencies.Cache(chatManager);
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual
return true;
}
- bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font == "Exo2.0-MediumItalic");
+ bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics);
bool isShowingLinks()
{
@@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual
Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay);
});
- AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}");
+ AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage));
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs
index f969ec69e2..5cbe97e21d 100644
--- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs
+++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs
@@ -61,10 +61,10 @@ namespace osu.Game.Tests.Visual
// Move box along a square trajectory
container.Loop(c => c
- .MoveTo(new Vector2(0, 100), duration).Then()
- .MoveTo(new Vector2(100, 100), duration).Then()
- .MoveTo(new Vector2(100, 0), duration).Then()
- .MoveTo(Vector2.Zero, duration)
+ .MoveTo(new Vector2(0, 100), duration).Then()
+ .MoveTo(new Vector2(100, 100), duration).Then()
+ .MoveTo(new Vector2(100, 0), duration).Then()
+ .MoveTo(Vector2.Zero, duration)
);
}
diff --git a/osu.Game.Tests/Visual/TestCaseDirectPanel.cs b/osu.Game.Tests/Visual/TestCaseDirectPanel.cs
new file mode 100644
index 0000000000..beb88ac56c
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseDirectPanel.cs
@@ -0,0 +1,47 @@
+// Copyright (c) ppy Pty Ltd . 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays.Direct;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Beatmaps;
+using osuTK;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseDirectPanel : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DirectGridPanel),
+ typeof(DirectListPanel),
+ typeof(IconPill)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
+ beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
+ beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
+
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding(20),
+ Spacing = new Vector2(0, 20),
+ Children = new Drawable[]
+ {
+ new DirectGridPanel(beatmap.BeatmapSetInfo),
+ new DirectListPanel(beatmap.BeatmapSetInfo)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs
index d3a53e4a05..8bba16e4b4 100644
--- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs
+++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs
@@ -2,27 +2,33 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
+using osu.Game.Online.API;
using osu.Game.Screens.Menu;
-using osuTK.Graphics;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
- public class TestCaseDisclaimer : OsuTestCase
+ public class TestCaseDisclaimer : ScreenTestCase
{
+ [Cached(typeof(IAPIProvider))]
+ private readonly DummyAPIAccess api = new DummyAPIAccess();
+
[BackgroundDependencyLoader]
private void load()
{
- Children = new Drawable[]
+ Add(api);
+
+ AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
+
+ AddStep("toggle support", () =>
{
- new Box
+ api.LocalUser.Value = new User
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- new Disclaimer()
- };
+ Username = api.LocalUser.Value.Username,
+ Id = api.LocalUser.Value.Id,
+ IsSupporter = !api.LocalUser.Value.IsSupporter,
+ };
+ });
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs b/osu.Game.Tests/Visual/TestCaseDrawableDate.cs
index 839f2a82de..8d2182dd78 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableDate.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual
}
};
- drawableDate.Current.ValueChanged += v => flash.FadeOutFromOne(500);
+ drawableDate.Current.ValueChanged += _ => flash.FadeOutFromOne(500);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs
index 51f34d54db..aad135b71f 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawings.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawings.cs
@@ -9,11 +9,11 @@ using osu.Game.Screens.Tournament.Teams;
namespace osu.Game.Tests.Visual
{
[Description("for tournament use")]
- public class TestCaseDrawings : OsuTestCase
+ public class TestCaseDrawings : ScreenTestCase
{
public TestCaseDrawings()
{
- Add(new Drawings
+ LoadScreen(new Drawings
{
TeamList = new TestTeamList(),
});
diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
index 66e13545d9..a52454d684 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, Clock);
Child = new ComposeScreen();
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
index 7e6edf78fc..9ae9b55546 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual
}
[BackgroundDependencyLoader]
- private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
+ private void load(IAdjustableClock adjustableClock, IBindable beatmap)
{
this.adjustableClock = adjustableClock;
this.beatmap.BindTo(beatmap);
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
index 6cb9a1abfd..0ec87e6f52 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual
{
TimingPoints =
{
- new TimingControlPoint { Time = 0, BeatLength = 200},
+ new TimingControlPoint { Time = 0, BeatLength = 200 },
new TimingControlPoint { Time = 100, BeatLength = 400 },
new TimingControlPoint { Time = 175, BeatLength = 800 },
new TimingControlPoint { Time = 350, BeatLength = 200 },
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual
}
};
- Beatmap.Value = new TestWorkingBeatmap(testBeatmap);
+ Beatmap.Value = new TestWorkingBeatmap(testBeatmap, Clock);
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
index b952582ef2..305924958b 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
Add(new SummaryTimeline
{
diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
index c5abe03dbd..c5ad57fec9 100644
--- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs
@@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual
[Description("player pause/fail screens")]
public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
+ public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
private FailOverlay failOverlay;
- private PauseContainer.PauseOverlay pauseOverlay;
+ private PauseOverlay pauseOverlay;
[BackgroundDependencyLoader]
private void load()
{
- Add(pauseOverlay = new PauseContainer.PauseOverlay
+ Add(pauseOverlay = new PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual
AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First()));
AddStep("Hide overlay", () => failOverlay.Hide());
- AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected));
+ AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value));
}
private void press(Key key)
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected);
+ AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
+ AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -132,11 +132,11 @@ namespace osu.Game.Tests.Visual
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
}
@@ -149,11 +149,11 @@ namespace osu.Game.Tests.Visual
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
}
@@ -169,8 +169,8 @@ namespace osu.Game.Tests.Visual
AddStep("Down arrow", () => press(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
- AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected);
- AddAssert("Second button selected", () => secondButton.Selected);
+ AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
+ AddAssert("Second button selected", () => secondButton.Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -190,8 +190,8 @@ namespace osu.Game.Tests.Visual
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Second button not selected", () => !secondButton.Selected);
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
+ AddAssert("Second button not selected", () => !secondButton.Selected.Value);
+ AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition
+ AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -265,6 +265,7 @@ namespace osu.Game.Tests.Visual
pauseOverlay.OnRetry = lastAction;
lastAction = null;
}
+
return triggered;
});
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
diff --git a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs
index 5ee1340044..a4fadbd3db 100644
--- a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs
+++ b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs
@@ -32,9 +32,9 @@ namespace osu.Game.Tests.Visual
var text = holdForMenuButton.Children.OfType().First();
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
- AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
+ AddUntilStep("Text visible", () => text.IsPresent && !exitAction);
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
- AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
+ AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction);
AddStep("Trigger exit action", () =>
{
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
AddAssert("action not triggered", () => !exitAction);
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
- AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered");
+ AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
index 046bd76f7e..c9a7e9c39f 100644
--- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Menu;
@@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Fired!",
- TextSize = 50,
+ Font = OsuFont.GetFont(size: 50),
Alpha = 0,
};
@@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual
AddStep("start confirming", () => overlay.Begin());
- AddUntilStep(() => fired, "wait until confirmed");
+ AddUntilStep("wait until confirmed", () => fired);
}
private class TestHoldToConfirmOverlay : ExitConfirmOverlay
diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs
index ea669af28c..a7a1831ba7 100644
--- a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs
+++ b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual
{
AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre));
- AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle");
+ AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1)));
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual
AddAssert("check idle", () => !box3.IsIdle);
AddAssert("check idle", () => !box4.IsIdle);
- AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle");
+ AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}
[Test]
@@ -96,13 +96,13 @@ namespace osu.Game.Tests.Visual
AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
- AddUntilStep(() => box1.IsIdle, "Wait for idle");
+ AddUntilStep("Wait for idle", () => box1.IsIdle);
AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
- AddUntilStep(() => box2.IsIdle, "Wait for idle");
+ AddUntilStep("Wait for idle", () => box2.IsIdle);
AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle);
- AddUntilStep(() => box3.IsIdle, "Wait for idle");
+ AddUntilStep("Wait for idle", () => box3.IsIdle);
- AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle");
+ AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}
private class IdleTrackingBox : CompositeDrawable
@@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual
},
};
- idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle ? Color4.White : Color4.Black, true);
+ idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle.NewValue ? Color4.White : Color4.Black, true);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
index 822809b96e..eb1a2c0249 100644
--- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs
@@ -20,7 +20,8 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase
{
- public override IReadOnlyList RequiredTypes => new[] {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
typeof(Placeholder),
typeof(MessagePlaceholder),
typeof(RetrievalFailurePlaceholder),
diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs
index 70ed075101..3803764194 100644
--- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs
+++ b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@@ -24,30 +25,30 @@ namespace osu.Game.Tests.Visual
bool logoVisible = false;
AddStep("almost instant display", () => Child = loader = new TestLoader(250));
- AddUntilStep(() =>
+ AddUntilStep("loaded", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
return loader.Logo != null && loader.ScreenLoaded;
- }, "loaded");
+ });
AddAssert("logo not visible", () => !logoVisible);
AddStep("short load", () => Child = loader = new TestLoader(800));
- AddUntilStep(() =>
+ AddUntilStep("loaded", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
return loader.Logo != null && loader.ScreenLoaded;
- }, "loaded");
+ });
AddAssert("logo visible", () => logoVisible);
- AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone");
+ AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
AddStep("longer load", () => Child = loader = new TestLoader(1400));
- AddUntilStep(() =>
+ AddUntilStep("loaded", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
return loader.Logo != null && loader.ScreenLoaded;
- }, "loaded");
+ });
AddAssert("logo visible", () => logoVisible);
- AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone");
+ AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
}
private class TestLoader : Loader
@@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual
public OsuLogo Logo;
private TestScreen screen;
- public bool ScreenLoaded => screen.IsCurrentScreen;
+ public bool ScreenLoaded => screen.IsCurrentScreen();
public TestLoader(double delay)
{
@@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual
{
public TestScreen()
{
- Child = new Box
+ InternalChild = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.DarkSlateGray,
@@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
base.LogoArriving(logo, resuming);
- Child.FadeInFromZero(200);
+ InternalChild.FadeInFromZero(200);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
index 1dfc3cdc60..13bc5e24d9 100644
--- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
@@ -16,7 +16,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual
{
- public class TestCaseLoungeRoomsContainer : OsuTestCase
+ public class TestCaseLoungeRoomsContainer : MultiplayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
AddStep("select first room", () => container.Rooms.First().Action?.Invoke());
- AddAssert("first room selected", () => container.SelectedRoom.Value == roomManager.Rooms.First());
+ AddAssert("first room selected", () => Room == roomManager.Rooms.First());
AddStep("join first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus);
@@ -71,7 +71,11 @@ namespace osu.Game.Tests.Visual
private class TestRoomManager : IRoomManager
{
- public event Action RoomsUpdated;
+ public event Action RoomsUpdated
+ {
+ add { }
+ remove { }
+ }
public readonly BindableList Rooms = new BindableList();
IBindableList IRoomManager.Rooms => Rooms;
@@ -85,10 +89,6 @@ namespace osu.Game.Tests.Visual
public void PartRoom()
{
}
-
- public void Filter(FilterCriteria criteria)
- {
- }
}
private class JoinedRoomStatus : RoomStatus
diff --git a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/TestCaseMatchHeader.cs
index c1664c99a3..296e5f24ac 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchHeader.cs
@@ -12,7 +12,7 @@ using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual
{
- public class TestCaseMatchHeader : OsuTestCase
+ public class TestCaseMatchHeader : MultiplayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -21,11 +21,7 @@ namespace osu.Game.Tests.Visual
public TestCaseMatchHeader()
{
- var room = new Room();
-
- var header = new Header(room);
-
- room.Playlist.Add(new PlaylistItem
+ Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
{
@@ -46,9 +42,9 @@ namespace osu.Game.Tests.Visual
}
});
- room.Type.Value = new GameTypeTimeshift();
+ Room.Type.Value = new GameTypeTimeshift();
- Child = header;
+ Child = new Header();
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs b/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs
index fabeb0aaa4..45092c5b93 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
diff --git a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/TestCaseMatchInfo.cs
index 57b21f2d79..901c4f1644 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchInfo.cs
@@ -14,7 +14,7 @@ using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseMatchInfo : OsuTestCase
+ public class TestCaseMatchInfo : MultiplayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -27,18 +27,15 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- var room = new Room();
+ Add(new Info());
- Info info = new Info(room);
- Add(info);
-
- AddStep(@"set name", () => room.Name.Value = @"Room Name?");
- AddStep(@"set availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
- AddStep(@"set status", () => room.Status.Value = new RoomStatusPlaying());
+ AddStep(@"set name", () => Room.Name.Value = @"Room Name?");
+ AddStep(@"set availability", () => Room.Availability.Value = RoomAvailability.FriendsOnly);
+ AddStep(@"set status", () => Room.Status.Value = new RoomStatusPlaying());
AddStep(@"set beatmap", () =>
{
- room.Playlist.Clear();
- room.Playlist.Add(new PlaylistItem
+ Room.Playlist.Clear();
+ Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
{
@@ -54,14 +51,14 @@ namespace osu.Game.Tests.Visual
});
});
- AddStep(@"change name", () => room.Name.Value = @"Room Name!");
- AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.InviteOnly);
- AddStep(@"change status", () => room.Status.Value = new RoomStatusOpen());
- AddStep(@"null beatmap", () => room.Playlist.Clear());
+ AddStep(@"change name", () => Room.Name.Value = @"Room Name!");
+ AddStep(@"change availability", () => Room.Availability.Value = RoomAvailability.InviteOnly);
+ AddStep(@"change status", () => Room.Status.Value = new RoomStatusOpen());
+ AddStep(@"null beatmap", () => Room.Playlist.Clear());
AddStep(@"change beatmap", () =>
{
- room.Playlist.Clear();
- room.Playlist.Add(new PlaylistItem
+ Room.Playlist.Clear();
+ Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
{
diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
index 110c7699cb..484a212a38 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs
@@ -6,29 +6,29 @@ using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
-using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual
{
- public class TestCaseMatchLeaderboard : OsuTestCase
+ public class TestCaseMatchLeaderboard : MultiplayerTestCase
{
public TestCaseMatchLeaderboard()
{
+ Room.RoomID.Value = 3;
+
Add(new MatchLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = MatchLeaderboardScope.Overall,
- Room = new Room { RoomID = { Value = 3 } }
});
}
[Resolved]
- private APIAccess api { get; set; }
+ private IAPIProvider api { get; set; }
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
index 174f39a702..716523c23c 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
@@ -11,22 +9,14 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseMatchParticipants : OsuTestCase
+ public class TestCaseMatchParticipants : MultiplayerTestCase
{
- private readonly Bindable maxParticipants = new Bindable();
- private readonly Bindable> users = new Bindable>();
-
public TestCaseMatchParticipants()
{
- Participants participants;
+ Add(new Participants { RelativeSizeAxes = Axes.Both });
- Add(participants = new Participants { RelativeSizeAxes = Axes.Both });
-
- participants.MaxParticipants.BindTo(maxParticipants);
- participants.Users.BindTo(users);
-
- AddStep(@"set max to null", () => maxParticipants.Value = null);
- AddStep(@"set users", () => users.Value = new[]
+ AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
+ AddStep(@"set users", () => Room.Participants.Value = new[]
{
new User
{
@@ -54,9 +44,9 @@ namespace osu.Game.Tests.Visual
},
});
- AddStep(@"set max", () => maxParticipants.Value = 10);
- AddStep(@"clear users", () => users.Value = new User[] { });
- AddStep(@"set max to null", () => maxParticipants.Value = null);
+ AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
+ AddStep(@"clear users", () => Room.Participants.Value = new User[] { });
+ AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseMatchResults.cs b/osu.Game.Tests/Visual/TestCaseMatchResults.cs
index 3ce03cf723..582c035e82 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchResults.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchResults.cs
@@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Online.Multiplayer;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Multi.Ranking;
@@ -19,7 +18,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
- public class TestCaseMatchResults : OsuTestCase
+ public class TestCaseMatchResults : MultiplayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -38,68 +37,52 @@ namespace osu.Game.Tests.Visual
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
- Child = new TestMatchResults(new ScoreInfo
+ Room.RoomID.Value = 1;
+ Room.Name.Value = "an awesome room";
+
+ LoadScreen(new TestMatchResults(new ScoreInfo
{
User = new User { Id = 10 },
- });
+ }));
}
private class TestMatchResults : MatchResults
{
- private readonly Room room;
-
public TestMatchResults(ScoreInfo score)
- : this(score, new Room
- {
- RoomID = { Value = 1 },
- Name = { Value = "an awesome room" }
- })
+ : base(score)
{
}
- public TestMatchResults(ScoreInfo score, Room room)
- : base(score, room)
- {
- this.room = room;
- }
-
- protected override IEnumerable CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap, room) };
+ protected override IEnumerable CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap.Value) };
}
private class TestRoomLeaderboardPageInfo : RoomLeaderboardPageInfo
{
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
- private readonly Room room;
- public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap, Room room)
- : base(score, beatmap, room)
+ public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
+ : base(score, beatmap)
{
this.score = score;
this.beatmap = beatmap;
- this.room = room;
}
- public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap, room);
+ public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap);
}
private class TestRoomLeaderboardPage : RoomLeaderboardPage
{
- public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap, Room room)
- : base(score, beatmap, room)
+ public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap)
+ : base(score, beatmap)
{
}
- protected override MatchLeaderboard CreateLeaderboard(Room room) => new TestMatchLeaderboard(room);
+ protected override MatchLeaderboard CreateLeaderboard() => new TestMatchLeaderboard();
}
private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
{
- public TestMatchLeaderboard(Room room)
- : base(room)
- {
- }
-
protected override APIRequest FetchScores(Action> scoresCallback)
{
var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
index 16240f0c45..11c7d3ef70 100644
--- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@@ -13,12 +13,11 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi;
-using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual
{
- public class TestCaseMatchSettingsOverlay : OsuTestCase
+ public class TestCaseMatchSettingsOverlay : MultiplayerTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -28,14 +27,14 @@ namespace osu.Game.Tests.Visual
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
- private Room room;
private TestRoomSettings settings;
[SetUp]
public void Setup() => Schedule(() =>
{
- room = new Room();
- settings = new TestRoomSettings(room)
+ Room = new Room();
+
+ settings = new TestRoomSettings
{
RelativeSizeAxes = Axes.Both,
State = Visibility.Visible
@@ -49,20 +48,20 @@ namespace osu.Game.Tests.Visual
{
AddStep("clear name and beatmap", () =>
{
- room.Name.Value = "";
- room.Playlist.Clear();
+ Room.Name.Value = "";
+ Room.Playlist.Clear();
});
- AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
+ AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set name", () => room.Name.Value = "Room name");
- AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
+ AddStep("set name", () => Room.Name.Value = "Room name");
+ AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set beatmap", () => room.Playlist.Add(new PlaylistItem { Beatmap = new DummyWorkingBeatmap().BeatmapInfo }));
- AddAssert("button enabled", () => settings.ApplyButton.Enabled);
+ AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = new DummyWorkingBeatmap().BeatmapInfo }));
+ AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
- AddStep("clear name", () => room.Name.Value = "");
- AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
+ AddStep("clear name", () => Room.Name.Value = "");
+ AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
[Test]
@@ -112,22 +111,17 @@ namespace osu.Game.Tests.Visual
settings.ApplyButton.Action.Invoke();
});
- AddUntilStep(() => !settings.ErrorText.IsPresent, "error not displayed");
+ AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent);
}
private class TestRoomSettings : MatchSettingsOverlay
{
- public new TriangleButton ApplyButton => base.ApplyButton;
+ public TriangleButton ApplyButton => Settings.ApplyButton;
- public new OsuTextBox NameField => base.NameField;
- public new OsuDropdown DurationField => base.DurationField;
+ public OsuTextBox NameField => Settings.NameField;
+ public OsuDropdown DurationField => Settings.DurationField;
- public new OsuSpriteText ErrorText => base.ErrorText;
-
- public TestRoomSettings(Room room)
- : base(room)
- {
- }
+ public OsuSpriteText ErrorText => Settings.ErrorText;
}
private class TestRoomManager : IRoomManager
@@ -136,7 +130,11 @@ namespace osu.Game.Tests.Visual
public Func CreateRequested;
- public event Action RoomsUpdated;
+ public event Action RoomsUpdated
+ {
+ add { }
+ remove { }
+ }
public IBindableList Rooms { get; } = null;
@@ -154,8 +152,6 @@ namespace osu.Game.Tests.Visual
public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
-
- public void Filter(FilterCriteria criteria) => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index e0c0b419af..cb7e783bee 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Osu.Mods;
using System.Linq;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Mods.Sections;
@@ -208,22 +208,22 @@ namespace osu.Game.Tests.Visual
{
checkLabelColor(Color4.White);
selectNext(mod);
- AddWaitStep(1, "wait for changing colour");
+ AddWaitStep("wait for changing colour", 1);
checkLabelColor(colour);
selectPrevious(mod);
- AddWaitStep(1, "wait for changing colour");
+ AddWaitStep("wait for changing colour", 1);
checkLabelColor(Color4.White);
}
private void testRankedText(Mod mod)
{
- AddWaitStep(1, "wait for fade");
+ AddWaitStep("wait for fade", 1);
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
selectNext(mod);
- AddWaitStep(1, "wait for fade");
+ AddWaitStep("wait for fade", 1);
AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
selectPrevious(mod);
- AddWaitStep(1, "wait for fade");
+ AddWaitStep("wait for fade", 1);
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
}
diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
index 998cdcbad1..f7802e2d08 100644
--- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
+++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Screens;
using osu.Game.Screens;
using osu.Game.Screens.Multi;
@@ -15,15 +16,15 @@ namespace osu.Game.Tests.Visual
{
int index = 0;
- OsuScreen currentScreen = new TestMultiplayerSubScreen(index);
+ ScreenStack screenStack = new ScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both };
Children = new Drawable[]
{
- currentScreen,
- new Header(currentScreen)
+ screenStack,
+ new Header(screenStack)
};
- AddStep("push multi screen", () => currentScreen.Push(currentScreen = new TestMultiplayerSubScreen(++index)));
+ AddStep("push multi screen", () => screenStack.CurrentScreen.Push(new TestMultiplayerSubScreen(++index)));
}
private class TestMultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen
diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
index 13419167a7..804e3c5b1f 100644
--- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
+++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
@@ -11,7 +11,7 @@ using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseMultiScreen : OsuTestCase
+ public class TestCaseMultiScreen : ScreenTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -24,9 +24,7 @@ namespace osu.Game.Tests.Visual
{
Multiplayer multi = new Multiplayer();
- AddStep(@"show", () => Add(multi));
- AddWaitStep(5);
- AddStep(@"exit", multi.Exit);
+ AddStep(@"show", () => LoadScreen(multi));
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
index 109c2ed916..9e70df91b6 100644
--- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state);
void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
- manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; };
+ manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
setState(Visibility.Visible);
AddStep(@"simple #1", sendHelloNotification);
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual
setState(Visibility.Hidden);
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
- AddWaitStep(5);
+ AddWaitStep("wait some", 5);
checkProgressingCount(0);
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33);
- AddWaitStep(10);
+ AddWaitStep("wait some", 10);
checkProgressingCount(0);
diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
index 16087b5ad8..9e649b92e4 100644
--- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
@@ -4,9 +4,10 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Screens;
+using osu.Framework.Platform;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@@ -20,8 +21,12 @@ namespace osu.Game.Tests.Visual
typeof(OsuLogo),
};
- public TestCaseOsuGame()
+ [BackgroundDependencyLoader]
+ private void load(GameHost host)
{
+ OsuGame game = new OsuGame();
+ game.SetHost(host);
+
Children = new Drawable[]
{
new Box
@@ -29,7 +34,7 @@ namespace osu.Game.Tests.Visual
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- new Loader()
+ game
};
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
index f9804655ea..41b029d69e 100644
--- a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
+using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
@@ -14,7 +16,10 @@ namespace osu.Game.Tests.Visual
Add(parallax = new ParallaxContainer
{
- Child = new BackgroundScreenDefault { Alpha = 0.8f }
+ Child = new ScreenStack(new BackgroundScreenDefault { Alpha = 0.8f })
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
});
AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs
new file mode 100644
index 0000000000..d5d2cebbab
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCasePause.cs
@@ -0,0 +1,152 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Screens;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCasePause : PlayerTestCase
+ {
+ protected new PausePlayer Player => (PausePlayer)base.Player;
+
+ public TestCasePause()
+ : base(new OsuRuleset())
+ {
+ }
+
+ [Test]
+ public void TestPauseResume()
+ {
+ pauseAndConfirm();
+ resumeAndConfirm();
+ }
+
+ [Test]
+ public void TestPauseTooSoon()
+ {
+ pauseAndConfirm();
+ resumeAndConfirm();
+
+ pause();
+
+ confirmClockRunning(true);
+ confirmPauseOverlayShown(false);
+ }
+
+ [Test]
+ public void TestExitTooSoon()
+ {
+ pauseAndConfirm();
+
+ resume();
+
+ AddStep("exit too soon", () => Player.Exit());
+
+ confirmClockRunning(true);
+ confirmPauseOverlayShown(false);
+
+ AddAssert("not exited", () => Player.IsCurrentScreen());
+ }
+
+ [Test]
+ public void TestPauseAfterFail()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
+
+ confirmClockRunning(false);
+
+ pause();
+
+ confirmClockRunning(false);
+ confirmPauseOverlayShown(false);
+
+ AddAssert("fail overlay still shown", () => Player.FailOverlayVisible);
+
+ exitAndConfirm();
+ }
+
+ [Test]
+ public void TestExitFromGameplay()
+ {
+ AddStep("exit", () => Player.Exit());
+
+ confirmPaused();
+
+ exitAndConfirm();
+ }
+
+ [Test]
+ public void TestExitFromPause()
+ {
+ pauseAndConfirm();
+ exitAndConfirm();
+ }
+
+ private void pauseAndConfirm()
+ {
+ pause();
+ confirmPaused();
+ }
+
+ private void resumeAndConfirm()
+ {
+ resume();
+ confirmResumed();
+ }
+
+ private void exitAndConfirm()
+ {
+ AddUntilStep("player not exited", () => Player.IsCurrentScreen());
+ AddStep("exit", () => Player.Exit());
+ confirmExited();
+ }
+
+ private void confirmPaused()
+ {
+ confirmClockRunning(false);
+ AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
+ }
+
+ private void confirmResumed()
+ {
+ confirmClockRunning(true);
+ confirmPauseOverlayShown(false);
+ }
+
+ private void confirmExited()
+ {
+ AddUntilStep("player exited", () => !Player.IsCurrentScreen());
+ }
+
+ private void pause() => AddStep("pause", () => Player.Pause());
+ private void resume() => AddStep("resume", () => Player.Resume());
+
+ private void confirmPauseOverlayShown(bool isShown) =>
+ AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
+
+ private void confirmClockRunning(bool isRunning) =>
+ AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
+
+ protected override bool AllowFail => true;
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer();
+
+ protected class PausePlayer : Player
+ {
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible;
+
+ public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
index 5de8a468e7..4a2cf24c6d 100644
--- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
@@ -8,9 +8,11 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.MathUtils;
+using osu.Framework.Platform;
+using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets;
@@ -24,7 +26,7 @@ using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCasePlaySongSelect : OsuTestCase
+ public class TestCasePlaySongSelect : ScreenTestCase
{
private BeatmapManager manager;
@@ -81,7 +83,7 @@ namespace osu.Game.Tests.Visual
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(GameHost host)
{
factory = new DatabaseContextFactory(LocalStorage);
factory.ResetDatabase();
@@ -95,30 +97,25 @@ namespace osu.Game.Tests.Visual
usage.Migrate();
Dependencies.Cache(rulesets = new RulesetStore(factory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, null, defaultBeatmap = Beatmap.Default));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, defaultBeatmap = Beatmap.Default));
Beatmap.SetDefault();
}
[SetUp]
- public virtual void SetUp()
- {
- Schedule(() =>
- {
- manager?.Delete(manager.GetAllUsableBeatmapSets());
- Child = songSelect = new TestSongSelect();
- });
- }
+ public virtual void SetUp() =>
+ Schedule(() => { manager?.Delete(manager.GetAllUsableBeatmapSets()); });
[Test]
public void TestDummy()
{
+ createSongSelect();
AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap);
- AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
+ AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
addManyTestMaps();
- AddWaitStep(3);
+ AddWaitStep("wait for select", 3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
}
@@ -126,8 +123,9 @@ namespace osu.Game.Tests.Visual
[Test]
public void TestSorting()
{
+ createSongSelect();
addManyTestMaps();
- AddWaitStep(3);
+ AddWaitStep("wait for add", 3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
@@ -141,29 +139,32 @@ namespace osu.Game.Tests.Visual
[Ignore("needs fixing")]
public void TestImportUnderDifferentRuleset()
{
+ createSongSelect();
changeRuleset(2);
importForRuleset(0);
- AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection");
+ AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
[Test]
public void TestImportUnderCurrentRuleset()
{
+ createSongSelect();
changeRuleset(2);
importForRuleset(2);
importForRuleset(1);
- AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 2, "has selection");
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
changeRuleset(1);
- AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 1, "has selection");
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1);
changeRuleset(0);
- AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection");
+ AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
[Test]
public void TestRulesetChangeResetsMods()
{
+ createSongSelect();
changeRuleset(0);
changeMods(new OsuModHardRock());
@@ -186,15 +187,16 @@ namespace osu.Game.Tests.Visual
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
AddAssert("empty mods", () => !selectedMods.Value.Any());
- void onModChange(IEnumerable mods) => modChangeIndex = actionIndex++;
- void onRulesetChange(RulesetInfo ruleset) => rulesetChangeIndex = actionIndex--;
+ void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++;
+ void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--;
}
[Test]
public void TestStartAfterUnMatchingFilterDoesNotStart()
{
+ createSongSelect();
addManyTestMaps();
- AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection");
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
bool startRequested = false;
@@ -220,6 +222,12 @@ namespace osu.Game.Tests.Visual
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));
+ private void createSongSelect()
+ {
+ AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect()));
+ AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
+ }
+
private void addManyTestMaps()
{
AddStep("import test maps", () =>
diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
index 15b96d394a..abcff24c67 100644
--- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
@@ -26,10 +26,10 @@ namespace osu.Game.Tests.Visual
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(200,100)
+ Size = new Vector2(200, 100)
};
- Beatmap.Value = new TestWorkingBeatmap(new Beatmap());
+ Beatmap.Value = new TestWorkingBeatmap(new Beatmap(), Clock);
Child = playback;
}
diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs
index fd3d838149..2bc416f7f4 100644
--- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs
@@ -3,7 +3,10 @@
using System.Threading;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Screens;
using osu.Game.Beatmaps;
+using osu.Game.Screens;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@@ -11,28 +14,44 @@ namespace osu.Game.Tests.Visual
public class TestCasePlayerLoader : ManualInputManagerTestCase
{
private PlayerLoader loader;
+ private readonly ScreenStack stack;
+
+ [Cached]
+ private BackgroundScreenStack backgroundStack;
+
+ public TestCasePlayerLoader()
+ {
+ InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both });
+ InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both });
+ }
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Beatmap.Value = new DummyWorkingBeatmap(game);
- AddStep("load dummy beatmap", () => Add(loader = new PlayerLoader(() => new Player
+ AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player
{
AllowPause = false,
AllowLeadIn = false,
AllowResults = false,
})));
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
- AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current");
+ AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen());
+
+ AddStep("exit loader", () => loader.Exit());
+
+ AddUntilStep("wait for no longer alive", () => !loader.IsAlive);
AddStep("load slow dummy beatmap", () =>
{
SlowLoadPlayer slow = null;
- Add(loader = new PlayerLoader(() => slow = new SlowLoadPlayer
+ stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer
{
AllowPause = false,
AllowLeadIn = false,
@@ -42,7 +61,7 @@ namespace osu.Game.Tests.Visual
Scheduler.AddDelayed(() => slow.Ready = true, 5000);
});
- AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current");
+ AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen());
}
protected class SlowLoadPlayer : Player
diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs
new file mode 100644
index 0000000000..3e009ae080
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Lists;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCasePlayerReferenceLeaking : AllPlayersTestCase
+ {
+ private readonly WeakList workingWeakReferences = new WeakList();
+
+ private readonly WeakList playerWeakReferences = new WeakList();
+
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("no leaked beatmaps", () =>
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ int count = 0;
+
+ workingWeakReferences.ForEachAlive(_ => count++);
+ return count == 1;
+ });
+
+ AddUntilStep("no leaked players", () =>
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ int count = 0;
+
+ playerWeakReferences.ForEachAlive(_ => count++);
+ return count == 1;
+ });
+ }
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock)
+ {
+ var working = base.CreateWorkingBeatmap(beatmap, clock);
+ workingWeakReferences.Add(working);
+ return working;
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ var player = base.CreatePlayer(ruleset);
+ playerWeakReferences.Add(player);
+ return player;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
index 68c44c7758..63f4f88948 100644
--- a/osu.Game.Tests/Visual/TestCasePollingComponent.cs
+++ b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
@@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual
});
[Test]
+ [Ignore("polling is threaded, and it's very hard to hook into it correctly")]
public void TestInstantPolling()
{
createPoller(true);
@@ -106,8 +107,11 @@ namespace osu.Game.Tests.Visual
private void checkCount(int checkValue)
{
- Logger.Log($"value is {count}");
- AddAssert($"count is {checkValue}", () => count == checkValue);
+ AddAssert($"count is {checkValue}", () =>
+ {
+ Logger.Log($"value is {count}");
+ return count == checkValue;
+ });
}
private void createPoller(bool instant) => AddStep("create poller", () =>
diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs
index c12015a019..3a7e2352f8 100644
--- a/osu.Game.Tests/Visual/TestCaseReplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplay.cs
@@ -4,25 +4,37 @@
using System.ComponentModel;
using System.Linq;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
[Description("Player instantiated with a replay.")]
- public class TestCaseReplay : TestCasePlayer
+ public class TestCaseReplay : AllPlayersTestCase
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here
- // to simulate setting a replay rather than having the replay already set for us
- Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
- var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value);
+ var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo);
- // Reset the mods
- Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay));
+ return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
+ }
- return new ReplayPlayer(dummyRulesetContainer.ReplayScore);
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
+ AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new HUDOverlay HUDOverlay => base.HUDOverlay;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score)
+ {
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs
index 403742a7b5..c2880c1ea2 100644
--- a/osu.Game.Tests/Visual/TestCaseResults.cs
+++ b/osu.Game.Tests/Visual/TestCaseResults.cs
@@ -16,7 +16,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseResults : OsuTestCase
+ public class TestCaseResults : ScreenTestCase
{
private BeatmapManager beatmaps;
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
- Add(new SoloResults(new ScoreInfo
+ LoadScreen(new SoloResults(new ScoreInfo
{
TotalScore = 2845370,
Accuracy = 0.98,
diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
index e4e80e4017..3519ea67a6 100644
--- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
+++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"Hit! :D", delegate
{
- score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0);
+ score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0);
comboCounter.Increment();
numerator++;
denominator++;
diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
index 0f34e4f10a..dad684689e 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -19,16 +19,18 @@ namespace osu.Game.Tests.Visual
public class TestCaseScreenBreadcrumbControl : OsuTestCase
{
private readonly ScreenBreadcrumbControl breadcrumbs;
- private Screen currentScreen, changedScreen;
+ private readonly ScreenStack screenStack;
public TestCaseScreenBreadcrumbControl()
{
- TestScreen startScreen;
OsuSpriteText titleText;
+ IScreen startScreen = new TestScreenOne();
+ screenStack = new ScreenStack(startScreen) { RelativeSizeAxes = Axes.Both };
+
Children = new Drawable[]
{
- currentScreen = startScreen = new TestScreenOne(),
+ screenStack,
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -37,7 +39,7 @@ namespace osu.Game.Tests.Visual
Spacing = new Vector2(10),
Children = new Drawable[]
{
- breadcrumbs = new ScreenBreadcrumbControl(startScreen)
+ breadcrumbs = new ScreenBreadcrumbControl(screenStack)
{
RelativeSizeAxes = Axes.X,
},
@@ -46,12 +48,7 @@ namespace osu.Game.Tests.Visual
},
};
- breadcrumbs.Current.ValueChanged += s =>
- {
- titleText.Text = $"Changed to {s.ToString()}";
- changedScreen = s;
- };
-
+ breadcrumbs.Current.ValueChanged += screen => titleText.Text = $"Changed to {screen.NewValue.ToString()}";
breadcrumbs.Current.TriggerChange();
waitForCurrent();
@@ -60,18 +57,14 @@ namespace osu.Game.Tests.Visual
pushNext();
waitForCurrent();
- AddStep(@"make start current", () =>
- {
- startScreen.MakeCurrent();
- currentScreen = startScreen;
- });
+ AddStep(@"make start current", () => startScreen.MakeCurrent());
waitForCurrent();
pushNext();
waitForCurrent();
AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2);
- AddStep(@"exit current", () => changedScreen.Exit());
- AddAssert(@"current screen is first", () => startScreen == changedScreen);
+ AddStep(@"exit current", () => screenStack.CurrentScreen.Exit());
+ AddAssert(@"current screen is first", () => startScreen == screenStack.CurrentScreen);
}
[BackgroundDependencyLoader]
@@ -80,8 +73,8 @@ namespace osu.Game.Tests.Visual
breadcrumbs.StripColour = colours.Blue;
}
- private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
- private void waitForCurrent() => AddUntilStep(() => currentScreen.IsCurrentScreen, "current screen");
+ private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext());
+ private void waitForCurrent() => AddUntilStep("current screen", () => screenStack.CurrentScreen.IsCurrentScreen());
private abstract class TestScreen : OsuScreen
{
@@ -91,14 +84,14 @@ namespace osu.Game.Tests.Visual
public TestScreen PushNext()
{
TestScreen screen = CreateNextScreen();
- Push(screen);
+ this.Push(screen);
return screen;
}
protected TestScreen()
{
- Child = new FillFlowContainer
+ InternalChild = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs b/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs
new file mode 100644
index 0000000000..94f01e9d32
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs
@@ -0,0 +1,153 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseSkinReloadable : OsuTestCase
+ {
+ [Test]
+ public void TestInitialLoad()
+ {
+ var secondarySource = new SecondarySource();
+ SkinConsumer consumer = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
+ }
+ };
+ });
+
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ [Test]
+ public void TestOverride()
+ {
+ var secondarySource = new SecondarySource();
+
+ SkinConsumer consumer = null;
+ Container target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ private class NamedBox : Container
+ {
+ public NamedBox(string name)
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new SpriteText
+ {
+ Font = OsuFont.Default.With(size: 40),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = name
+ }
+ };
+ }
+ }
+
+ private class SkinConsumer : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+ public int SkinChangedCount { get; private set; }
+
+ public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
+ : base(name, defaultImplementation, allowFallback, restrictSize)
+ {
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+ SkinChangedCount++;
+ }
+ }
+
+ private class BaseSourceBox : NamedBox
+ {
+ public BaseSourceBox()
+ : base("Base Source")
+ {
+ }
+ }
+
+ private class SecondarySourceBox : NamedBox
+ {
+ public SecondarySourceBox()
+ : base("Secondary Source")
+ {
+ }
+ }
+
+ private class SecondarySource : ISkinSource
+ {
+ public event Action SourceChanged;
+
+ public void TriggerSourceChanged() => SourceChanged?.Invoke();
+
+ public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+
+ private class SkinSourceContainer : Container, ISkinSource
+ {
+ public event Action SourceChanged;
+
+ public void TriggerSourceChanged() => SourceChanged?.Invoke();
+
+ public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs
index 9ce33f21d6..511272a5ae 100644
--- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs
+++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
@@ -15,23 +16,29 @@ namespace osu.Game.Tests.Visual
public class TestCaseSongProgress : OsuTestCase
{
private readonly SongProgress progress;
- private readonly SongProgressGraph graph;
+ private readonly TestSongProgressGraph graph;
private readonly StopwatchClock clock;
+ [Cached]
+ private readonly GameplayClock gameplayClock;
+
+ private readonly FramedClock framedClock;
+
public TestCaseSongProgress()
{
clock = new StopwatchClock(true);
+ gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
+
Add(progress = new SongProgress
{
RelativeSizeAxes = Axes.X,
- AudioClock = new StopwatchClock(true),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
});
- Add(graph = new SongProgressGraph
+ Add(graph = new TestSongProgressGraph
{
RelativeSizeAxes = Axes.X,
Height = 200,
@@ -39,13 +46,24 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.TopLeft,
});
+ AddWaitStep("wait some", 5);
+ AddAssert("ensure not created", () => graph.CreationCount == 0);
+
+ AddStep("display values", displayNewValues);
+ AddWaitStep("wait some", 5);
+ AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
+
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
- AddWaitStep(5);
+ AddWaitStep("wait some", 5);
+ AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
+
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
- AddWaitStep(2);
+ AddWaitStep("wait some", 5);
+ AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
AddRepeatStep("New Values", displayNewValues, 5);
- displayNewValues();
+ AddWaitStep("wait some", 5);
+ AddAssert("ensure debounced", () => graph.CreationCount == 2);
}
private void displayNewValues()
@@ -57,8 +75,24 @@ namespace osu.Game.Tests.Visual
progress.Objects = objects;
graph.Objects = objects;
- progress.AudioClock = clock;
- progress.OnSeek = pos => clock.Seek(pos);
+ progress.RequestSeek = pos => clock.Seek(pos);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ framedClock.ProcessFrame();
+ }
+
+ private class TestSongProgressGraph : SongProgressGraph
+ {
+ public int CreationCount { get; private set; }
+
+ protected override void RecreateGraph()
+ {
+ base.RecreateGraph();
+ CreationCount++;
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs
index a4ad213116..c4b41e40f4 100644
--- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -49,7 +50,10 @@ namespace osu.Game.Tests.Visual
});
AddStep("Restart", restart);
- AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; });
+ AddToggleStep("Passing", passing =>
+ {
+ if (storyboard != null) storyboard.Passing = passing;
+ });
}
[BackgroundDependencyLoader]
@@ -58,15 +62,15 @@ namespace osu.Game.Tests.Visual
Beatmap.ValueChanged += beatmapChanged;
}
- private void beatmapChanged(WorkingBeatmap working)
- => loadStoryboard(working);
+ private void beatmapChanged(ValueChangedEvent e)
+ => loadStoryboard(e.NewValue);
private void restart()
{
var track = Beatmap.Value.Track;
track.Reset();
- loadStoryboard(Beatmap);
+ loadStoryboard(Beatmap.Value);
track.Start();
}
@@ -78,7 +82,7 @@ namespace osu.Game.Tests.Visual
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
storyboardContainer.Clock = decoupledClock;
- storyboard = working.Storyboard.CreateDrawable(Beatmap);
+ storyboard = working.Storyboard.CreateDrawable(Beatmap.Value);
storyboard.Passing = false;
storyboardContainer.Add(storyboard);
diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/TestCaseTabControl.cs
index 82f56cb0f8..ebf8f3bb30 100644
--- a/osu.Game.Tests/Visual/TestCaseTabControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseTabControl.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual
filter.PinItem(GroupMode.All);
filter.PinItem(GroupMode.RecentlyPlayed);
- filter.Current.ValueChanged += newFilter =>
+ filter.Current.ValueChanged += grouping =>
{
- text.Text = "Currently Selected: " + newFilter.ToString();
+ text.Text = "Currently Selected: " + grouping.NewValue.ToString();
};
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs
index be1b75823a..cb5f33911b 100644
--- a/osu.Game.Tests/Visual/TestCaseToolbar.cs
+++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs
@@ -24,10 +24,13 @@ namespace osu.Game.Tests.Visual
public TestCaseToolbar()
{
var toolbar = new Toolbar { State = Visibility.Visible };
+ ToolbarNotificationButton notificationButton = null;
- Add(toolbar);
-
- var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First();
+ AddStep("create toolbar", () =>
+ {
+ Add(toolbar);
+ notificationButton = toolbar.Children.OfType().Last().Children.OfType().First();
+ });
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);
diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs
index 14f178a293..0981b482a1 100644
--- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs
@@ -3,7 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
@@ -16,42 +16,48 @@ namespace osu.Game.Tests.Visual
{
public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase
{
- private UpdateableBeatmapBackgroundSprite backgroundSprite;
+ private TestUpdateableBeatmapBackgroundSprite backgroundSprite;
[Resolved]
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
- private void load(OsuGameBase osu, APIAccess api, RulesetStore rulesets)
+ private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets)
{
Bindable beatmapBindable = new Bindable();
var imported = ImportBeatmapTest.LoadOszIntoOsu(osu);
- Child = backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
+ Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
backgroundSprite.Beatmap.BindTo(beatmapBindable);
var req = new GetBeatmapSetRequest(1);
api.Queue(req);
- AddStep("null", () => beatmapBindable.Value = null);
-
- AddStep("imported", () => beatmapBindable.Value = imported.Beatmaps.First());
+ AddStep("load null beatmap", () => beatmapBindable.Value = null);
+ AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1);
+ AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First());
+ AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1);
if (api.IsLoggedIn)
{
- AddUntilStep(() => req.Result != null, "wait for api response");
-
- AddStep("online", () => beatmapBindable.Value = new BeatmapInfo
+ AddUntilStep("wait for api response", () => req.Result != null);
+ AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo
{
BeatmapSet = req.Result?.ToBeatmapSet(rulesets)
});
+ AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1);
}
else
{
AddStep("online (login first)", () => { });
}
}
+
+ private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
+ {
+ public int ChildCount => InternalChildren.Count;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
index 726134294e..aa0bd37449 100644
--- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs
+++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual
public class TestCaseUserProfile : OsuTestCase
{
private readonly TestUserProfileOverlay profile;
- private APIAccess api;
+ private IAPIProvider api;
public override IReadOnlyList RequiredTypes => new[]
{
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual
}
[BackgroundDependencyLoader]
- private void load(APIAccess api)
+ private void load(IAPIProvider api)
{
this.api = api;
}
@@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual
private void checkSupporterTag(bool isSupporter)
{
- AddUntilStep(() => profile.Header.User != null, "wait for load");
+ AddUntilStep("wait for load", () => profile.Header.User != null);
if (isSupporter)
AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1);
else
diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs
index 56dcfc3cbb..07a282a1a7 100644
--- a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- TextSize = 20,
+ Font = OsuFont.GetFont(size: 20),
Text = @"Wave Container",
},
},
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 72db4b0c17..2028671b0e 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Archives;
-using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Resources;
namespace osu.Game.Tests
{
@@ -18,12 +18,12 @@ namespace osu.Game.Tests
public class WaveformTestBeatmap : WorkingBeatmap
{
private readonly ZipArchiveReader reader;
- private readonly FileStream stream;
+ private readonly Stream stream;
public WaveformTestBeatmap()
: base(new BeatmapInfo())
{
- stream = File.OpenRead(ImportBeatmapTest.TEST_OSZ_PATH);
+ stream = TestResources.GetTestBeatmapStream();
reader = new ZipArchiveReader(stream);
}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index b22c1aed99..938e1ae0f8 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,9 +3,9 @@
-
+
-
+
diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs
index 5abf6bdb08..3b21bdefc4 100644
--- a/osu.Game/Audio/PreviewTrack.cs
+++ b/osu.Game/Audio/PreviewTrack.cs
@@ -28,6 +28,7 @@ namespace osu.Game.Audio
private void load()
{
track = GetTrack();
+ track.Completed += () => Schedule(Stop);
}
///
@@ -50,15 +51,6 @@ namespace osu.Game.Audio
///
public bool IsRunning => track?.IsRunning ?? false;
- protected override void Update()
- {
- base.Update();
-
- // Todo: Track currently doesn't signal its completion, so we have to handle it manually
- if (hasStarted && track.HasCompleted)
- Stop();
- }
-
private ScheduledDelegate startDelegate;
///
@@ -71,6 +63,7 @@ namespace osu.Game.Audio
if (hasStarted)
return;
+
hasStarted = true;
track.Restart();
@@ -89,6 +82,7 @@ namespace osu.Game.Audio
if (!hasStarted)
return;
+
hasStarted = false;
track.Stop();
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
index 6de7d0e4f7..99c0d70ac9 100644
--- a/osu.Game/Audio/PreviewTrackManager.cs
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index 736caf69e8..5bc6dce60b 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -50,12 +50,14 @@ namespace osu.Game.Audio
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
+
yield return $"{Namespace}/{Bank}-{Name}";
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
+
yield return $"{Bank}-{Name}";
}
}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index d6041ad38d..b6fa6674f6 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Beatmaps
where T : HitObject
{
private event Action> ObjectConverted;
+
event Action> IBeatmapConverter.ObjectConverted
{
add => ObjectConverted += value;
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 21b943e111..8727431e0e 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -48,6 +48,7 @@ namespace osu.Game.Beatmaps
return mid + (max - mid) * (difficulty - 5) / 5;
if (difficulty < 5)
return mid - (mid - min) * (5 - difficulty) / 5;
+
return mid;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 42048692fc..9caa64ec96 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -50,11 +49,6 @@ namespace osu.Game.Beatmaps
///
public event Action BeatmapDownloadFailed;
- ///
- /// Fired when a beatmap load is requested (into the interactive game UI).
- ///
- public Action PresentBeatmap;
-
///
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
///
@@ -70,7 +64,7 @@ namespace osu.Game.Beatmaps
private readonly BeatmapStore beatmaps;
- private readonly APIAccess api;
+ private readonly IAPIProvider api;
private readonly AudioManager audioManager;
@@ -78,7 +72,7 @@ namespace osu.Game.Beatmaps
private readonly List currentDownloads = new List();
- public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, GameHost host = null,
+ public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, new BeatmapStore(contextFactory), host)
{
@@ -108,10 +102,16 @@ namespace osu.Game.Beatmaps
b.BeatmapSet = beatmapSet;
}
- validateOnlineIds(beatmapSet.Beatmaps);
+ validateOnlineIds(beatmapSet);
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
- fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps);
+ fetchAndPopulateOnlineValues(b);
+ }
+
+ protected override void PreImport(BeatmapSetInfo beatmapSet)
+ {
+ if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null))
+ throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}.");
// check if a set already exists with the same online id, delete if it does.
if (beatmapSet.OnlineBeatmapSetID != null)
@@ -126,14 +126,30 @@ namespace osu.Game.Beatmaps
}
}
- private void validateOnlineIds(List beatmaps)
+ private void validateOnlineIds(BeatmapSetInfo beatmapSet)
{
- var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
+ var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
- // ensure all IDs are unique in this set and none match existing IDs in the local beatmap store.
- if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any())
- // remove all online IDs if any problems were found.
- beatmaps.ForEach(b => b.OnlineBeatmapID = null);
+ // ensure all IDs are unique
+ if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
+ {
+ resetIds();
+ return;
+ }
+
+ // find any existing beatmaps in the database that have matching online ids
+ var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList();
+
+ if (existingBeatmaps.Count > 0)
+ {
+ // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set.
+ // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted.
+ var existing = CheckForExisting(beatmapSet);
+ if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b)))
+ resetIds();
+ }
+
+ void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null);
}
///
@@ -151,8 +167,7 @@ namespace osu.Game.Beatmaps
var downloadNotification = new DownloadNotification
{
- CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!",
- Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
+ Text = $"Downloading {beatmapSetInfo}",
};
var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo);
@@ -163,26 +178,12 @@ namespace osu.Game.Beatmaps
downloadNotification.Progress = progress;
};
- request.Success += data =>
+ request.Success += filename =>
{
- downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}";
-
Task.Factory.StartNew(() =>
{
- BeatmapSetInfo importedBeatmap;
-
// This gets scheduled back to the update thread, but we want the import to run in the background.
- using (var stream = new MemoryStream(data))
- using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString()))
- importedBeatmap = Import(archive);
-
- downloadNotification.CompletionClickAction = () =>
- {
- PresentCompletedImport(importedBeatmap.Yield());
- return true;
- };
- downloadNotification.State = ProgressNotificationState.Completed;
-
+ Import(downloadNotification, filename);
currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
};
@@ -210,17 +211,21 @@ namespace osu.Game.Beatmaps
PostNotification?.Invoke(downloadNotification);
// don't run in the main api queue as this is a long-running task.
- Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning);
+ Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ request.Perform(api);
+ }
+ catch (Exception e)
+ {
+ // no need to handle here as exceptions will filter down to request.Failure above.
+ }
+ }, TaskCreationOptions.LongRunning);
BeatmapDownloadBegan?.Invoke(request);
return true;
}
- protected override void PresentCompletedImport(IEnumerable imported)
- {
- base.PresentCompletedImport(imported);
- PresentBeatmap?.Invoke(imported.LastOrDefault());
- }
-
///
/// Get an existing download request if it exists.
///
@@ -271,6 +276,18 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
+ protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import)
+ {
+ if (!base.CanUndelete(existing, import))
+ return false;
+
+ var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
+ var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
+
+ // force re-import if we are not in a sane state.
+ return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds);
+ }
+
///
/// Returns a list of all usable s.
///
@@ -368,7 +385,7 @@ namespace osu.Game.Beatmaps
/// The other beatmaps contained within this set.
/// Whether to re-query if the provided beatmap already has populated values.
/// True if population was successful.
- private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false)
+ private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false)
{
if (api?.State != APIState.Online)
return false;
@@ -391,13 +408,6 @@ namespace osu.Game.Beatmaps
beatmap.Status = res.Status;
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
-
- if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID))
- {
- Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database);
- return false;
- }
-
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index bac8ad5ed7..001f319307 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -34,8 +34,8 @@ namespace osu.Game.Beatmaps
[Column("Author")]
public string AuthorString
{
- get { return Author?.Username; }
- set { Author = new User { Username = value }; }
+ get => Author?.Username;
+ set => Author = new User { Username = value };
}
///
@@ -48,6 +48,7 @@ namespace osu.Game.Beatmaps
[JsonProperty(@"tags")]
public string Tags { get; set; }
+
public int PreviewTime { get; set; }
public string AudioFile { get; set; }
public string BackgroundFile { get; set; }
@@ -72,15 +73,15 @@ namespace osu.Game.Beatmaps
return false;
return Title == other.Title
- && TitleUnicode == other.TitleUnicode
- && Artist == other.Artist
- && ArtistUnicode == other.ArtistUnicode
- && AuthorString == other.AuthorString
- && Source == other.Source
- && Tags == other.Tags
- && PreviewTime == other.PreviewTime
- && AudioFile == other.AudioFile
- && BackgroundFile == other.BackgroundFile;
+ && TitleUnicode == other.TitleUnicode
+ && Artist == other.Artist
+ && ArtistUnicode == other.ArtistUnicode
+ && AuthorString == other.AuthorString
+ && Source == other.Source
+ && Tags == other.Tags
+ && PreviewTime == other.PreviewTime
+ && AudioFile == other.AudioFile
+ && BackgroundFile == other.BackgroundFile;
}
}
}
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index ad7648e7fd..f4b7b1d74f 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
///
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
///
- public class BeatmapStore : MutableDatabaseBackedStore
+ public class BeatmapStore : MutableDatabaseBackedStoreWithFileIncludes
{
public event Action BeatmapHidden;
public event Action BeatmapRestored;
@@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps
Refresh(ref beatmap, Beatmaps);
if (beatmap.Hidden) return false;
+
beatmap.Hidden = true;
}
@@ -53,6 +54,7 @@ namespace osu.Game.Beatmaps
Refresh(ref beatmap, Beatmaps);
if (!beatmap.Hidden) return false;
+
beatmap.Hidden = false;
}
@@ -62,18 +64,17 @@ namespace osu.Game.Beatmaps
protected override IQueryable AddIncludesForDeletion(IQueryable query) =>
base.AddIncludesForDeletion(query)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
- .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Metadata)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Scores);
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Scores)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);
protected override IQueryable AddIncludesForConsumption(IQueryable query) =>
base.AddIncludesForConsumption(query)
.Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
- .Include(s => s.Files).ThenInclude(f => f.FileInfo);
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);
protected override void Purge(List items, OsuDbContext context)
{
diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs
index bbd0fbfb06..657dc06297 100644
--- a/osu.Game/Beatmaps/BindableBeatmap.cs
+++ b/osu.Game/Beatmaps/BindableBeatmap.cs
@@ -6,15 +6,15 @@ using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
namespace osu.Game.Beatmaps
{
///
- /// A for the beatmap.
- /// This should be used sparingly in-favour of .
+ /// A for the beatmap.
+ /// This should be used sparingly in-favour of .
///
- public abstract class BindableBeatmap : NonNullableBindable, IBindableBeatmap
+ public abstract class BindableBeatmap : NonNullableBindable
{
private AudioManager audioManager;
private WorkingBeatmap lastBeatmap;
@@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps
this.audioManager = audioManager;
- ValueChanged += registerAudioTrack;
+ ValueChanged += b => registerAudioTrack(b.NewValue);
// If the track has changed prior to this being called, let's register it
if (Value != Default)
@@ -62,9 +62,6 @@ namespace osu.Game.Beatmaps
lastBeatmap = beatmap;
}
- [NotNull]
- IBindableBeatmap IBindableBeatmap.GetBoundCopy() => GetBoundCopy();
-
///
/// Retrieve a new instance weakly bound to this .
/// If you are further binding to events of the retrieved , ensure a local reference is held.
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
index c8c735b439..0c59eec1ef 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs
@@ -13,8 +13,8 @@ namespace osu.Game.Beatmaps.Drawables
public BeatmapBackgroundSprite(WorkingBeatmap working)
{
- if (working == null)
- throw new ArgumentNullException(nameof(working));
+ if (working == null)
+ throw new ArgumentNullException(nameof(working));
this.working = working;
}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
deleted file mode 100644
index 4b3ee04eca..0000000000
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Configuration;
-using osu.Framework.Graphics;
-using osu.Game.Online.API.Requests;
-
-namespace osu.Game.Beatmaps.Drawables
-{
- ///
- /// A component to allow downloading of a beatmap set. Automatically handles state syncing between other instances.
- ///
- public class BeatmapSetDownloader : Component
- {
- private readonly BeatmapSetInfo set;
- private readonly bool noVideo;
-
- private BeatmapManager beatmaps;
-
- ///
- /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded.
- ///
- public readonly Bindable DownloadState = new Bindable();
-
- public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false)
- {
- this.set = set;
- this.noVideo = noVideo;
- }
-
- [BackgroundDependencyLoader]
- private void load(BeatmapManager beatmaps)
- {
- this.beatmaps = beatmaps;
-
- beatmaps.ItemAdded += setAdded;
- beatmaps.ItemRemoved += setRemoved;
- beatmaps.BeatmapDownloadBegan += downloadBegan;
- beatmaps.BeatmapDownloadFailed += downloadFailed;
-
- // initial value
- if (set.OnlineBeatmapSetID != null && beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any())
- DownloadState.Value = DownloadStatus.Downloaded;
- else if (beatmaps.GetExistingDownload(set) != null)
- DownloadState.Value = DownloadStatus.Downloading;
- else
- DownloadState.Value = DownloadStatus.NotDownloaded;
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmaps != null)
- {
- beatmaps.ItemAdded -= setAdded;
- beatmaps.ItemRemoved -= setRemoved;
- beatmaps.BeatmapDownloadBegan -= downloadBegan;
- beatmaps.BeatmapDownloadFailed -= downloadFailed;
- }
- }
-
- ///
- /// Begin downloading the associated beatmap set.
- ///
- /// True if downloading began. False if an existing download is active or completed.
- public void Download()
- {
- if (DownloadState.Value > DownloadStatus.NotDownloaded)
- return;
-
- if (beatmaps.Download(set, noVideo))
- {
- // Only change state if download can happen
- DownloadState.Value = DownloadStatus.Downloading;
- }
- }
-
- private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() =>
- {
- if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
- DownloadState.Value = DownloadStatus.Downloaded;
- });
-
- private void setRemoved(BeatmapSetInfo s) => Schedule(() =>
- {
- if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
- DownloadState.Value = DownloadStatus.NotDownloaded;
- });
-
- private void downloadBegan(DownloadBeatmapSetRequest d)
- {
- if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
- DownloadState.Value = DownloadStatus.Downloading;
- }
-
- private void downloadFailed(DownloadBeatmapSetRequest d)
- {
- if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
- DownloadState.Value = DownloadStatus.NotDownloaded;
- }
-
- public enum DownloadStatus
- {
- NotDownloaded,
- Downloading,
- Downloaded,
- }
- }
-}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
index 5e20ca8bc8..351e5df17a 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
@@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -22,6 +23,7 @@ namespace osu.Game.Beatmaps.Drawables
{
if (status == value)
return;
+
status = value;
Alpha = value == BeatmapSetOnlineStatus.None ? 0 : 1;
@@ -31,8 +33,8 @@ namespace osu.Game.Beatmaps.Drawables
public float TextSize
{
- get => statusText.TextSize;
- set => statusText.TextSize = value;
+ get => statusText.Font.Size;
+ set => statusText.Font = statusText.Font.With(size: value);
}
public MarginPadding TextPadding
@@ -58,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Font = @"Exo2.0-Bold",
+ Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
};
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
index b025b5985c..f1607ad749 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
@@ -48,11 +48,12 @@ namespace osu.Game.Beatmaps.Drawables
var rating = beatmap.StarDifficulty;
- if (rating < 1.5) return DifficultyRating.Easy;
- if (rating < 2.25) return DifficultyRating.Normal;
- if (rating < 3.75) return DifficultyRating.Hard;
- if (rating < 5.25) return DifficultyRating.Insane;
- if (rating < 6.75) return DifficultyRating.Expert;
+ if (rating < 2.0) return DifficultyRating.Easy;
+ if (rating < 2.7) return DifficultyRating.Normal;
+ if (rating < 4.0) return DifficultyRating.Hard;
+ if (rating < 5.3) return DifficultyRating.Insane;
+ if (rating < 6.5) return DifficultyRating.Expert;
+
return DifficultyRating.ExpertPlus;
}
diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
index c66052052f..ce7811fc0d 100644
--- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
@@ -2,49 +2,69 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Beatmaps.Drawables
{
///
- /// Display a baetmap background from a local source, but fallback to online source if not available.
+ /// Display a beatmap background from a local source, but fallback to online source if not available.
///
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable
{
- public readonly IBindable Beatmap = new Bindable