mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 07:33:20 +08:00
Merge branch 'profile-header-update' of github.com:Jorolf/osu into profile-header-update
This commit is contained in:
commit
0ac4bbfcf0
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@ -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.
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -11,8 +11,10 @@
|
||||
*.userprefs
|
||||
|
||||
### Cake ###
|
||||
tools/*
|
||||
!tools/cakebuild.csproj
|
||||
tools/**
|
||||
build/tools/**
|
||||
|
||||
fastlane/report.xml
|
||||
|
||||
# Build results
|
||||
bin/[Dd]ebug/
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "osu-resources"]
|
||||
path = osu-resources
|
||||
url = https://github.com/ppy/osu-resources
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
3
.vscode/tasks.json
vendored
3
.vscode/tasks.json
vendored
@ -70,7 +70,8 @@
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore"
|
||||
"restore",
|
||||
"osu.sln"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
|
6
Gemfile
Normal file
6
Gemfile
Normal file
@ -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)
|
173
Gemfile.lock
Normal file
173
Gemfile.lock
Normal file
@ -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
|
38
README.md
38
README.md
@ -1,22 +1,24 @@
|
||||
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
||||
# osu!
|
||||
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
||||
|
||||
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.
|
||||
|
||||
|
13
build.ps1
13
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
|
||||
|
3
build.sh
3
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=()
|
||||
|
@ -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
|
||||
});
|
||||
});
|
2
fastlane/Appfile
Normal file
2
fastlane/Appfile
Normal file
@ -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
|
65
fastlane/Fastfile
Normal file
65
fastlane/Fastfile
Normal file
@ -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
|
1
fastlane/Matchfile
Normal file
1
fastlane/Matchfile
Normal file
@ -0,0 +1 @@
|
||||
git_url('https://github.com/peppy/apple-certificates')
|
7
fastlane/Pluginfile
Normal file
7
fastlane/Pluginfile
Normal file
@ -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'
|
54
fastlane/README.md
Normal file
54
fastlane/README.md
Normal file
@ -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).
|
@ -1 +0,0 @@
|
||||
Subproject commit 677897728f4332fa1200e0280ca02c4b987c6c47
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -22,7 +22,6 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
|
@ -39,10 +39,6 @@
|
||||
<Project>{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}</Project>
|
||||
<Name>osu.Game.Rulesets.Catch</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
|
||||
<Project>{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}</Project>
|
||||
<Name>osu.Game.Resources</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" />
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.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.2038001515546597d, "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();
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestFixture]
|
||||
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
|
||||
{
|
||||
public TestCaseCatchPlayer() : base(new CatchRuleset())
|
||||
public TestCaseCatchPlayer()
|
||||
: base(new CatchRuleset())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
@ -17,13 +19,30 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
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 hperdash
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void AddCheckSteps(Func<Player> player)
|
||||
{
|
||||
base.AddCheckSteps(player);
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
[Description("Move left")]
|
||||
MoveLeft,
|
||||
|
||||
[Description("Move right")]
|
||||
MoveRight,
|
||||
|
||||
[Description("Engage dash")]
|
||||
Dash,
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +1,92 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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.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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private const double strain_step = 750;
|
||||
|
||||
/// <summary>
|
||||
/// The weighting of each strain value decays to this number * it's previous value
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.94;
|
||||
|
||||
private const double star_scaling_factor = 0.145;
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
private readonly float halfCatchWidth;
|
||||
|
||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
|
||||
halfCatchWidth = catcher.CatchWidth * 0.5f;
|
||||
|
||||
// We're only using 80% of the catcher's width to simulate imperfect gameplay.
|
||||
halfCatchWidth *= 0.8f;
|
||||
}
|
||||
|
||||
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<CatchDifficultyHitObject>();
|
||||
|
||||
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<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet))
|
||||
};
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
CatchHitObject lastObject = null;
|
||||
|
||||
foreach (var hitObject in beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
{
|
||||
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<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
|
||||
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().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<CatchDifficultyHitObject> objects, double timeRate)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
{
|
||||
CatchDifficultyHitObject lastObject = null;
|
||||
|
||||
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<CatchDifficultyHitObject> objects, double timeRate)
|
||||
{
|
||||
// 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>();
|
||||
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 Movement(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using 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;
|
||||
|
||||
/// <summary>
|
||||
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
|
||||
/// </summary>
|
||||
internal double Strain = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using 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;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
85
osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Normal file
85
osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
||||
/// <returns>The random value.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
@ -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<int> 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";
|
||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||
base.SkinChanged(skin, allowFallback);
|
||||
|
||||
if (HitObject is IHasComboInformation combo)
|
||||
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : Color4.White);
|
||||
AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
|
||||
}
|
||||
|
||||
private const float preempt = 1000;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -55,6 +55,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
var minDistanceFromEnd = Velocity * 0.01;
|
||||
|
||||
var tickSamples = Samples.Select(s => new SampleInfo
|
||||
{
|
||||
Bank = s.Bank,
|
||||
Name = @"slidertick",
|
||||
Volume = s.Volume
|
||||
}).ToList();
|
||||
|
||||
AddNested(new Fruit
|
||||
{
|
||||
Samples = Samples,
|
||||
@ -62,15 +69,22 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
X = X
|
||||
});
|
||||
|
||||
double lastDropletTime = StartTime;
|
||||
double lastTickTime = StartTime;
|
||||
|
||||
for (int span = 0; span < this.SpanCount(); span++)
|
||||
{
|
||||
var spanStartTime = StartTime + span * spanDuration;
|
||||
var reversed = span % 2 == 1;
|
||||
|
||||
for (double d = 0; d <= length; d += tickDistance)
|
||||
for (double d = tickDistance;; d += tickDistance)
|
||||
{
|
||||
bool isLastTick = false;
|
||||
if (d + minDistanceFromEnd >= length)
|
||||
{
|
||||
d = length;
|
||||
isLastTick = true;
|
||||
}
|
||||
|
||||
var timeProgress = d / length;
|
||||
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
|
||||
|
||||
@ -79,47 +93,42 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
if (LegacyLastTickOffset != null)
|
||||
{
|
||||
// If we're the last tick, apply the legacy offset
|
||||
if (span == this.SpanCount() - 1 && d + tickDistance > length)
|
||||
if (span == this.SpanCount() - 1 && isLastTick)
|
||||
time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
|
||||
}
|
||||
|
||||
double tinyTickInterval = time - lastDropletTime;
|
||||
while (tinyTickInterval > 100)
|
||||
tinyTickInterval /= 2;
|
||||
|
||||
for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
|
||||
int tinyTickCount = 1;
|
||||
double tinyTickInterval = time - lastTickTime;
|
||||
while (tinyTickInterval > 100 && tinyTickCount < 10000)
|
||||
{
|
||||
tinyTickInterval /= 2;
|
||||
tinyTickCount *= 2;
|
||||
}
|
||||
|
||||
for (int tinyTickIndex = 0; tinyTickIndex < tinyTickCount - 1; tinyTickIndex++)
|
||||
{
|
||||
var t = lastTickTime + (tinyTickIndex + 1) * tinyTickInterval;
|
||||
double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
|
||||
|
||||
AddNested(new TinyDroplet
|
||||
{
|
||||
StartTime = t,
|
||||
X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
|
||||
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
|
||||
{
|
||||
Bank = s.Bank,
|
||||
Name = @"slidertick",
|
||||
Volume = s.Volume
|
||||
}))
|
||||
Samples = tickSamples
|
||||
});
|
||||
}
|
||||
|
||||
if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
|
||||
lastTickTime = time;
|
||||
|
||||
if (isLastTick)
|
||||
break;
|
||||
|
||||
AddNested(new Droplet
|
||||
{
|
||||
AddNested(new Droplet
|
||||
{
|
||||
StartTime = time,
|
||||
X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
|
||||
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
|
||||
{
|
||||
Bank = s.Bank,
|
||||
Name = @"slidertick",
|
||||
Volume = s.Volume
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
lastDropletTime = time;
|
||||
StartTime = time,
|
||||
X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
|
||||
Samples = tickSamples
|
||||
});
|
||||
}
|
||||
|
||||
AddNested(new Fruit
|
||||
|
@ -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
|
@ -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}]}]}
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
@ -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
|
||||
/// </summary>
|
||||
protected bool Trail
|
||||
{
|
||||
get { return trail; }
|
||||
get => trail;
|
||||
set
|
||||
{
|
||||
if (value == trail) return;
|
||||
|
@ -39,10 +39,6 @@
|
||||
<Project>{48F4582B-7687-4621-9CBE-5C24197CB536}</Project>
|
||||
<Name>osu.Game.Rulesets.Mania</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
|
||||
<Project>{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}</Project>
|
||||
<Name>osu.Game.Resources</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" />
|
||||
|
@ -40,29 +40,29 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
||||
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
|
||||
{
|
||||
public uint RandomW;
|
||||
public uint RandomX;
|
||||
public uint RandomY;
|
||||
public uint RandomZ;
|
||||
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
|
||||
{
|
||||
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<ConvertValue> 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<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
||||
}
|
||||
|
||||
public struct ConvertValue : IEquatable<ConvertValue>
|
||||
{
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
protected override IEnumerable<Type> 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<ManiaHitObject> CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
|
||||
protected override Beatmap<ManiaHitObject> CreateBeatmap()
|
||||
{
|
||||
beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
|
||||
|
||||
if (Dual)
|
||||
beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ManiaHitObject> 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<double> prevNoteTimes = new List<double>(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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -12,51 +12,63 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
internal enum PatternType
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Keep the same as last row.
|
||||
/// </summary>
|
||||
ForceStack = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Keep different from last row.
|
||||
/// </summary>
|
||||
ForceNotStack = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Keep as single note at its original position.
|
||||
/// </summary>
|
||||
KeepSingle = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Use a lower random value.
|
||||
/// </summary>
|
||||
LowProbability = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Reserved.
|
||||
/// </summary>
|
||||
Alternate = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Ignore the repeat count.
|
||||
/// </summary>
|
||||
ForceSigSlider = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Convert slider to circle.
|
||||
/// </summary>
|
||||
ForceNotSlider = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// Notes gathered together.
|
||||
/// </summary>
|
||||
Gathered = 1 << 7,
|
||||
Mirror = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// Change 0 -> 6.
|
||||
/// </summary>
|
||||
Reverse = 1 << 9,
|
||||
|
||||
/// <summary>
|
||||
/// 1 -> 5 -> 1 -> 5 like reverse.
|
||||
/// </summary>
|
||||
Cycle = 1 << 10,
|
||||
|
||||
/// <summary>
|
||||
/// Next note will be at column + 1.
|
||||
/// </summary>
|
||||
Stair = 1 << 11,
|
||||
|
||||
/// <summary>
|
||||
/// Next note will be at column - 1.
|
||||
/// </summary>
|
||||
|
@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Configuration
|
||||
{
|
||||
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
|
||||
public class ManiaRulesetConfigManager : RulesetConfigManager<ManiaRulesetSetting>
|
||||
{
|
||||
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<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
|
||||
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
|
||||
};
|
||||
}
|
||||
|
||||
public enum ManiaSetting
|
||||
public enum ManiaRulesetSetting
|
||||
{
|
||||
ScrollTime,
|
||||
ScrollDirection
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private const double strain_step = 400;
|
||||
|
||||
/// <summary>
|
||||
/// The weighting of each strain value decays to this number * it's previous value
|
||||
/// </summary>
|
||||
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<ManiaHitObjectDifficulty>();
|
||||
|
||||
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<ManiaHitObjectDifficulty> 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<Overall>().Single();
|
||||
var aggregatePeaks = new List<double>(Enumerable.Repeat(0.0, overall.StrainPeaks.Count));
|
||||
|
||||
foreach (var individual in skills.OfType<Individual>())
|
||||
{
|
||||
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<ManiaHitObjectDifficulty> objects, double timeRate)
|
||||
{
|
||||
double actualStrainStep = strain_step * timeRate;
|
||||
|
||||
// Find the highest strain value within each strain step
|
||||
List<double> highestStrains = new List<double>();
|
||||
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<DifficultyHitObject> 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<Skill> { new Overall(columnCount) };
|
||||
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
skills.Add(new Individual(i, columnCount));
|
||||
|
||||
return skills.ToArray();
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods
|
||||
{
|
||||
get
|
||||
|
@ -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));
|
||||
|
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -162,7 +162,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 +330,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 +349,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
/// Number of columns in this stage lies at (item - Single).
|
||||
/// </summary>
|
||||
Single = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Columns are grouped into two stages.
|
||||
/// Overall number of columns lies at (item - Dual), further computation is required for
|
||||
|
@ -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<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = "Scrolling direction",
|
||||
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
|
||||
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<double, TimeSlider>
|
||||
{
|
||||
LabelText = "Scroll speed",
|
||||
Bindable = config.GetBindable<double>(ManiaSetting.ScrollTime)
|
||||
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
|
||||
/// <returns>The random value.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,15 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using 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<ManiaHitObject>
|
||||
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<ManiaHitObject> beatmap)
|
||||
{
|
||||
if (isForCurrentRuleset)
|
||||
return;
|
||||
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
var newDefinitions = new List<StageDefinition>();
|
||||
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;
|
||||
|
@ -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<int> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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<ScrollingDirection> 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;
|
||||
|
@ -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;
|
||||
|
@ -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<ScrollingDirection> e)
|
||||
{
|
||||
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.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<ScrollingDirection> 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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.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;
|
||||
}
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
internal class ManiaHitObjectDifficulty
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor by how much individual / overall strain decays per second.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These values are results of tweaking a lot and taking into account general feedback.
|
||||
/// </remarks>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Measures jacks or more generally: repeated presses of the same button
|
||||
/// </summary>
|
||||
private readonly double[] individualStrains;
|
||||
|
||||
internal double IndividualStrain
|
||||
{
|
||||
get
|
||||
{
|
||||
return individualStrains[BaseHitObject.Column];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
individualStrains[BaseHitObject.Column] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures note density in a way
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
|
||||
{
|
||||
{ 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) },
|
||||
|
@ -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:
|
@ -159,6 +159,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
||||
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
||||
}
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -19,7 +19,7 @@ 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()
|
||||
|
@ -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;
|
||||
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public IEnumerable<BarLine> BarLines;
|
||||
|
||||
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
|
||||
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
|
||||
@ -75,10 +74,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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
||||
|
||||
public override int Variant => (int)(Mods.OfType<IPlayfieldTypeMod>().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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -39,10 +39,6 @@
|
||||
<Project>{C92A607B-1FDD-4954-9F92-03FF547D9080}</Project>
|
||||
<Name>osu.Game.Rulesets.Osu</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
|
||||
<Project>{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}</Project>
|
||||
<Name>osu.Game.Resources</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
<Import Project="..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.0\build\netstandard2.0\NETStandard.Library.targets')" />
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
25
osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Normal file
25
osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.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();
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
private GameplayCursor cursor;
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new [] { typeof(CursorTrail) };
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
|
||||
|
||||
public CursorContainer Cursor => cursor;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
36
osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
Normal file
36
osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
});
|
||||
|
||||
|
372
osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
Normal file
372
osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
Normal file
@ -0,0 +1,372 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using 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<Type> 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<JudgementResult> judgementResults;
|
||||
private bool allJudgedFired;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestInvalidKeyTransfer()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLeftBeforeSliderThenRightThenLettingGoOfLeft()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingRetentionLeftRightLeft()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingLeftBeforeSliderToRight()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingPreclicked()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
|
||||
});
|
||||
|
||||
AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingReturnMidSlider()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingReturnMidSliderKeyDownBefore()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingMidSlider()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestMidSliderTrackingAcquired()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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<ReplayFrame>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTrackingReleasedValidKey()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
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<ReplayFrame> 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<OsuHitObject>
|
||||
{
|
||||
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<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep(() => Beatmap.Value.Track.CurrentTime == 0, "Beatmap at 0");
|
||||
AddUntilStep(() => currentPlayer.IsCurrentScreen(), "Wait until player is loaded");
|
||||
AddUntilStep(() => allJudgedFired, "Wait for all judged");
|
||||
}
|
||||
|
||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public class TestCaseSpinner : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> 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;
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Configuration
|
||||
{
|
||||
public class OsuRulesetConfigManager : RulesetConfigManager<OsuRulesetSetting>
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user