mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 06:03:22 +08:00
Merge remote-tracking branch 'upstream/master' into tournament-tools
This commit is contained in:
commit
796f6c3092
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
|
*.userprefs
|
||||||
|
|
||||||
### Cake ###
|
### Cake ###
|
||||||
tools/*
|
tools/**
|
||||||
!tools/cakebuild.csproj
|
build/tools/**
|
||||||
|
|
||||||
|
fastlane/report.xml
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
bin/[Dd]ebug/
|
bin/[Dd]ebug/
|
||||||
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -68,6 +68,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
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
|
13
build.ps1
13
build.ps1
@ -41,27 +41,28 @@ Param(
|
|||||||
[switch]$ShowDescription,
|
[switch]$ShowDescription,
|
||||||
[Alias("WhatIf", "Noop")]
|
[Alias("WhatIf", "Noop")]
|
||||||
[switch]$DryRun,
|
[switch]$DryRun,
|
||||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
[Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$ScriptArgs
|
[string[]]$ScriptArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
Write-Host "Preparing to run build script..."
|
Write-Host "Preparing to run build script..."
|
||||||
|
|
||||||
# Determine the script root for resolving other paths.
|
# Determine the script root for resolving other paths.
|
||||||
if(!$PSScriptRoot){
|
if(!$PSScriptRoot) {
|
||||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||||
}
|
}
|
||||||
|
|
||||||
# Resolve the paths for resources used for debugging.
|
# Resolve the paths for resources used for debugging.
|
||||||
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
|
$BUILD_DIR = Join-Path $PSScriptRoot "build"
|
||||||
$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj"
|
$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
|
||||||
|
$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
|
||||||
|
|
||||||
# Install the required tools locally.
|
# Install the required tools locally.
|
||||||
Write-Host "Restoring cake tools..."
|
Write-Host "Restoring cake tools..."
|
||||||
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
|
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
|
||||||
|
|
||||||
# Find the Cake executable
|
# 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
|
# Build Cake arguments
|
||||||
$cakeArguments = @("$Script");
|
$cakeArguments = @("$Script");
|
||||||
@ -75,5 +76,7 @@ $cakeArguments += $ScriptArgs
|
|||||||
|
|
||||||
# Start Cake
|
# Start Cake
|
||||||
Write-Host "Running build script..."
|
Write-Host "Running build script..."
|
||||||
|
Push-Location -Path $BUILD_DIR
|
||||||
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
|
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
|
||||||
|
Pop-Location
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
||||||
|
3
build.sh
3
build.sh
@ -6,12 +6,13 @@
|
|||||||
|
|
||||||
echo "Preparing to run build script..."
|
echo "Preparing to run build script..."
|
||||||
|
|
||||||
|
cd build
|
||||||
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||||
TOOLS_DIR=$SCRIPT_DIR/tools
|
TOOLS_DIR=$SCRIPT_DIR/tools
|
||||||
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
|
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
|
||||||
|
|
||||||
SCRIPT="build.cake"
|
SCRIPT="build.cake"
|
||||||
CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj"
|
CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
|
||||||
|
|
||||||
# Parse arguments.
|
# Parse arguments.
|
||||||
CAKE_ARGUMENTS=()
|
CAKE_ARGUMENTS=()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#addin "nuget:?package=CodeFileSanity&version=0.0.21"
|
#addin "nuget:?package=CodeFileSanity&version=0.0.21"
|
||||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2"
|
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2"
|
||||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||||
|
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// ARGUMENTS
|
// ARGUMENTS
|
||||||
@ -9,30 +10,24 @@
|
|||||||
var target = Argument("target", "Build");
|
var target = Argument("target", "Build");
|
||||||
var configuration = Argument("configuration", "Release");
|
var configuration = Argument("configuration", "Release");
|
||||||
|
|
||||||
var osuSolution = new FilePath("./osu.sln");
|
var rootDirectory = new DirectoryPath("..");
|
||||||
|
var solution = rootDirectory.CombineWithFilePath("osu.sln");
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// TASKS
|
// TASKS
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Task("Restore")
|
|
||||||
.Does(() => {
|
|
||||||
DotNetCoreRestore(osuSolution.FullPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
Task("Compile")
|
Task("Compile")
|
||||||
.IsDependentOn("Restore")
|
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings {
|
DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings {
|
||||||
Configuration = configuration,
|
Configuration = configuration,
|
||||||
NoRestore = true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Test")
|
Task("Test")
|
||||||
.IsDependentOn("Compile")
|
.IsDependentOn("Compile")
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll");
|
var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll");
|
||||||
|
|
||||||
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
|
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
|
||||||
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
|
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
|
||||||
@ -46,9 +41,7 @@ Task("InspectCode")
|
|||||||
.WithCriteria(IsRunningOnWindows())
|
.WithCriteria(IsRunningOnWindows())
|
||||||
.IsDependentOn("Compile")
|
.IsDependentOn("Compile")
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
InspectCode(solution, new InspectCodeSettings {
|
||||||
|
|
||||||
InspectCode(osuSolution, new InspectCodeSettings {
|
|
||||||
CachesHome = "inspectcode",
|
CachesHome = "inspectcode",
|
||||||
OutputFile = "inspectcodereport.xml",
|
OutputFile = "inspectcodereport.xml",
|
||||||
});
|
});
|
||||||
@ -59,7 +52,7 @@ Task("InspectCode")
|
|||||||
Task("CodeFileSanity")
|
Task("CodeFileSanity")
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
ValidateCodeSanity(new ValidateCodeSanitySettings {
|
ValidateCodeSanity(new ValidateCodeSanitySettings {
|
||||||
RootDirectory = ".",
|
RootDirectory = rootDirectory.FullPath,
|
||||||
IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
|
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).
|
@ -83,8 +83,7 @@ namespace osu.Desktop
|
|||||||
public override void SetHost(GameHost host)
|
public override void SetHost(GameHost host)
|
||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
var desktopWindow = host.Window as DesktopGameWindow;
|
if (host.Window is DesktopGameWindow desktopWindow)
|
||||||
if (desktopWindow != null)
|
|
||||||
{
|
{
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Desktop.Overlays
|
|||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Font = @"Exo2.0-Bold",
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
Text = game.Name
|
Text = game.Name
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
@ -74,9 +74,8 @@ namespace osu.Desktop.Overlays
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
TextSize = 12,
|
Font = OsuFont.Numeric.With(size: 12),
|
||||||
Colour = colours.Yellow,
|
Colour = colours.Yellow,
|
||||||
Font = @"Venera",
|
|
||||||
Text = @"Development Build"
|
Text = @"Development Build"
|
||||||
},
|
},
|
||||||
new Sprite
|
new Sprite
|
||||||
|
@ -34,13 +34,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
case JuiceStream stream:
|
case JuiceStream stream:
|
||||||
foreach (var nested in stream.NestedHitObjects)
|
foreach (var nested in stream.NestedHitObjects)
|
||||||
yield return new ConvertValue((CatchHitObject)nested);
|
yield return new ConvertValue((CatchHitObject)nested);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case BananaShower shower:
|
case BananaShower shower:
|
||||||
foreach (var nested in shower.NestedHitObjects)
|
foreach (var nested in shower.NestedHitObjects)
|
||||||
yield return new ConvertValue((CatchHitObject)nested);
|
yield return new ConvertValue((CatchHitObject)nested);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
yield return new ConvertValue((CatchHitObject)hitObject);
|
yield return new ConvertValue((CatchHitObject)hitObject);
|
||||||
|
|
||||||
break;
|
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]
|
[TestFixture]
|
||||||
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
|
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
|
||||||
{
|
{
|
||||||
public TestCaseCatchPlayer() : base(new CatchRuleset())
|
public TestCaseCatchPlayer()
|
||||||
|
: base(new CatchRuleset())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 rotation
|
||||||
rng.Next(); // osu!stable retrieved a random banana colour
|
rng.Next(); // osu!stable retrieved a random banana colour
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case JuiceStream juiceStream:
|
case JuiceStream juiceStream:
|
||||||
foreach (var nested in juiceStream.NestedHitObjects)
|
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
|
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||||
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
|
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
{
|
{
|
||||||
[Description("Move left")]
|
[Description("Move left")]
|
||||||
MoveLeft,
|
MoveLeft,
|
||||||
|
|
||||||
[Description("Move right")]
|
[Description("Move right")]
|
||||||
MoveRight,
|
MoveRight,
|
||||||
|
|
||||||
[Description("Engage dash")]
|
[Description("Engage dash")]
|
||||||
Dash,
|
Dash,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public int MaxCombo;
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Catch.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
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;
|
private const double star_scaling_factor = 0.145;
|
||||||
|
|
||||||
|
protected override int SectionLength => 750;
|
||||||
|
|
||||||
|
private readonly float halfCatchWidth;
|
||||||
|
|
||||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(ruleset, 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())
|
if (beatmap.HitObjects.Count == 0)
|
||||||
return new CatchDifficultyAttributes(mods, 0);
|
return new CatchDifficultyAttributes { Mods = mods };
|
||||||
|
|
||||||
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
|
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||||
float halfCatchWidth = catcher.CatchWidth * 0.5f;
|
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
|
|
||||||
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
|
return new CatchDifficultyAttributes
|
||||||
|
|
||||||
foreach (var hitObject in beatmap.HitObjects)
|
|
||||||
{
|
{
|
||||||
|
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)
|
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.
|
// 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:
|
case Fruit fruit:
|
||||||
difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth));
|
yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
|
||||||
|
|
||||||
|
lastObject = hitObject;
|
||||||
break;
|
break;
|
||||||
case JuiceStream _:
|
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;
|
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;
|
new Movement(),
|
||||||
|
};
|
||||||
if (!objects.Any()) return false;
|
|
||||||
|
|
||||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
|
||||||
foreach (var currentObject in objects)
|
|
||||||
{
|
|
||||||
if (lastObject != null)
|
|
||||||
currentObject.CalculateStrains(lastObject, timeRate);
|
|
||||||
|
|
||||||
lastObject = currentObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double calculateDifficulty(List<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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
/// <returns>The random value.</returns>
|
||||||
public uint NextUInt()
|
public uint NextUInt()
|
||||||
{
|
{
|
||||||
uint t = _x ^ _x << 11;
|
uint t = _x ^ (_x << 11);
|
||||||
_x = _y;
|
_x = _y;
|
||||||
_y = _z;
|
_y = _z;
|
||||||
_z = _w;
|
_z = _w;
|
||||||
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
|
return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
return default_flashlight_size;
|
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";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
|
|
||||||
public override Color4 AccentColour
|
public override Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return base.AccentColour; }
|
get => base.AccentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.AccentColour = value;
|
base.AccentColour = value;
|
||||||
|
@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return accentColour; }
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
@ -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
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class CatcherArea : Container
|
public class CatcherArea : Container
|
||||||
{
|
{
|
||||||
public const float CATCHER_SIZE = 100;
|
public const float CATCHER_SIZE = 106.75f;
|
||||||
|
|
||||||
protected internal readonly Catcher MovableCatcher;
|
protected internal readonly Catcher MovableCatcher;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public Container ExplodingFruitTarget
|
||||||
{
|
{
|
||||||
set { MovableCatcher.ExplodingFruitTarget = value; }
|
set => MovableCatcher.ExplodingFruitTarget = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
public CatcherArea(BeatmapDifficulty difficulty = null)
|
||||||
@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
protected bool Dashing
|
protected bool Dashing
|
||||||
{
|
{
|
||||||
get { return dashing; }
|
get => dashing;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == dashing) return;
|
if (value == dashing) return;
|
||||||
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool Trail
|
protected bool Trail
|
||||||
{
|
{
|
||||||
get { return trail; }
|
get => trail;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == trail) return;
|
if (value == trail) return;
|
||||||
|
@ -40,29 +40,29 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
|
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
|
||||||
{
|
{
|
||||||
public uint RandomW;
|
public uint RandomW;
|
||||||
public uint RandomX;
|
public uint RandomX;
|
||||||
public uint RandomY;
|
public uint RandomY;
|
||||||
public uint RandomZ;
|
public uint RandomZ;
|
||||||
|
|
||||||
public ManiaConvertMapping()
|
public ManiaConvertMapping()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManiaConvertMapping(IBeatmapConverter converter)
|
public ManiaConvertMapping(IBeatmapConverter converter)
|
||||||
{
|
{
|
||||||
var maniaConverter = (ManiaBeatmapConverter)converter;
|
var maniaConverter = (ManiaBeatmapConverter)converter;
|
||||||
RandomW = maniaConverter.Random.W;
|
RandomW = maniaConverter.Random.W;
|
||||||
RandomX = maniaConverter.Random.X;
|
RandomX = maniaConverter.Random.X;
|
||||||
RandomY = maniaConverter.Random.Y;
|
RandomY = maniaConverter.Random.Y;
|
||||||
RandomZ = maniaConverter.Random.Z;
|
RandomZ = maniaConverter.Random.Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
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 override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ConvertValue : IEquatable<ConvertValue>
|
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 NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
TextSize = 14,
|
Font = OsuFont.GetFont(size: 14),
|
||||||
Text = description
|
Text = description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
||||||
|
|
||||||
public int TargetColumns;
|
public int TargetColumns;
|
||||||
|
public bool Dual;
|
||||||
public readonly bool IsForCurrentRuleset;
|
public readonly bool IsForCurrentRuleset;
|
||||||
|
|
||||||
// Internal for testing purposes
|
// Internal for testing purposes
|
||||||
@ -45,7 +46,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||||
|
|
||||||
if (IsForCurrentRuleset)
|
if (IsForCurrentRuleset)
|
||||||
|
{
|
||||||
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
||||||
|
if (TargetColumns >= 10)
|
||||||
|
{
|
||||||
|
TargetColumns = TargetColumns / 2;
|
||||||
|
Dual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
|
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);
|
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)
|
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaOriginal = original as ManiaHitObject;
|
if (original is ManiaHitObject maniaOriginal)
|
||||||
if (maniaOriginal != null)
|
|
||||||
{
|
{
|
||||||
yield return maniaOriginal;
|
yield return maniaOriginal;
|
||||||
|
|
||||||
yield break;
|
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 readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
|
||||||
private double density = int.MaxValue;
|
private double density = int.MaxValue;
|
||||||
|
|
||||||
private void computeDensity(double newNoteTime)
|
private void computeDensity(double newNoteTime)
|
||||||
{
|
{
|
||||||
if (prevNoteTimes.Count == max_notes_for_density)
|
if (prevNoteTimes.Count == max_notes_for_density)
|
||||||
@ -104,6 +121,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
private double lastTime;
|
private double lastTime;
|
||||||
private Vector2 lastPosition;
|
private Vector2 lastPosition;
|
||||||
private PatternType lastStair = PatternType.Stair;
|
private PatternType lastStair = PatternType.Stair;
|
||||||
|
|
||||||
private void recordNote(double time, Vector2 position)
|
private void recordNote(double time, Vector2 position)
|
||||||
{
|
{
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
|
@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
if (originalPattern.HitObjects.Count() == 1)
|
if (originalPattern.HitObjects.Count() == 1)
|
||||||
{
|
{
|
||||||
yield return originalPattern;
|
yield return originalPattern;
|
||||||
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
|
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
|
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))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
|
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 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))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
|
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
|
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
|
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.27, 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 (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||||
// If we convert to 7K + 1, let's not overload the special key
|
// If we convert to 7K + 1, let's not overload the special key
|
||||||
&& (TotalColumns != 8 || lastColumn != 0)
|
&& (TotalColumns != 8 || lastColumn != 0)
|
||||||
// Make sure the last column was not the centre column
|
// Make sure the last column was not the centre column
|
||||||
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
|
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
|
||||||
{
|
{
|
||||||
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
|
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
|
||||||
int column = RandomStart + TotalColumns - lastColumn - 1;
|
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);
|
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
|
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPatternWithMirrored(0.12, 0, 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))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
|
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(1, 0.62, 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))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
|
return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(0.52, 0.15, 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))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return pattern = generateRandomPattern(0.18, 0, 0, 0);
|
return pattern = generateRandomPattern(0.18, 0, 0, 0);
|
||||||
|
|
||||||
return pattern = generateRandomPattern(0.45, 0, 0, 0);
|
return pattern = generateRandomPattern(0.45, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +254,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
last = GetRandomColumn();
|
last = GetRandomColumn();
|
||||||
|
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
return 4;
|
return 4;
|
||||||
if (val >= 1 - p3)
|
if (val >= 1 - p3)
|
||||||
return 3;
|
return 3;
|
||||||
|
|
||||||
return val >= 1 - p2 ? 2 : 1;
|
return val >= 1 - p2 ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,51 +12,63 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
internal enum PatternType
|
internal enum PatternType
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keep the same as last row.
|
/// Keep the same as last row.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ForceStack = 1 << 0,
|
ForceStack = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keep different from last row.
|
/// Keep different from last row.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ForceNotStack = 1 << 1,
|
ForceNotStack = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keep as single note at its original position.
|
/// Keep as single note at its original position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
KeepSingle = 1 << 2,
|
KeepSingle = 1 << 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use a lower random value.
|
/// Use a lower random value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LowProbability = 1 << 3,
|
LowProbability = 1 << 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved.
|
/// Reserved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Alternate = 1 << 4,
|
Alternate = 1 << 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ignore the repeat count.
|
/// Ignore the repeat count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ForceSigSlider = 1 << 5,
|
ForceSigSlider = 1 << 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert slider to circle.
|
/// Convert slider to circle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ForceNotSlider = 1 << 6,
|
ForceNotSlider = 1 << 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notes gathered together.
|
/// Notes gathered together.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Gathered = 1 << 7,
|
Gathered = 1 << 7,
|
||||||
Mirror = 1 << 8,
|
Mirror = 1 << 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change 0 -> 6.
|
/// Change 0 -> 6.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Reverse = 1 << 9,
|
Reverse = 1 << 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 1 -> 5 -> 1 -> 5 like reverse.
|
/// 1 -> 5 -> 1 -> 5 like reverse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Cycle = 1 << 10,
|
Cycle = 1 << 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Next note will be at column + 1.
|
/// Next note will be at column + 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Stair = 1 << 11,
|
Stair = 1 << 11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Next note will be at column - 1.
|
/// Next note will be at column - 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2,17 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
{
|
{
|
||||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double GreatHitWindow;
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
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.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.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
{
|
{
|
||||||
internal class ManiaDifficultyCalculator : DifficultyCalculator
|
public class ManiaDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double star_scaling_factor = 0.018;
|
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;
|
private readonly bool isForCurrentRuleset;
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
@ -37,108 +27,70 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
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())
|
if (beatmap.HitObjects.Count == 0)
|
||||||
return new ManiaDifficultyAttributes(mods, 0);
|
return new ManiaDifficultyAttributes { Mods = mods };
|
||||||
|
|
||||||
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
return new ManiaDifficultyAttributes
|
||||||
|
|
||||||
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 ManiaDifficultyAttributes(mods, 0);
|
|
||||||
|
|
||||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
|
||||||
|
|
||||||
return new ManiaDifficultyAttributes(mods, starRating)
|
|
||||||
{
|
{
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
StarRating = difficultyValue(skills) * star_scaling_factor,
|
||||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
|
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.
|
// Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section
|
||||||
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
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())
|
for (int i = 0; i < individual.StrainPeaks.Count; i++)
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
var next = hitObjectsEnumerator.Current;
|
double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i];
|
||||||
next?.CalculateStrains(current, timeRate);
|
|
||||||
current = next;
|
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 difficulty = 0;
|
||||||
double weight = 1;
|
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;
|
difficulty += strain * weight;
|
||||||
weight *= decay_weight;
|
weight *= 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
return difficulty;
|
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
|
protected override Mod[] DifficultyAdjustmentMods
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
// Lots of arbitrary values from testing.
|
// Lots of arbitrary values from testing.
|
||||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||||
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
|
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
|
||||||
* strainValue
|
* strainValue
|
||||||
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
|
* 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
|
// 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));
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
[Description("Special 1")]
|
[Description("Special 1")]
|
||||||
Special1 = 1,
|
Special1 = 1,
|
||||||
|
|
||||||
[Description("Special 2")]
|
[Description("Special 2")]
|
||||||
Special2,
|
Special2,
|
||||||
|
|
||||||
@ -26,38 +27,55 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
// above at a later time, without breaking replays/configs.
|
// above at a later time, without breaking replays/configs.
|
||||||
[Description("Key 1")]
|
[Description("Key 1")]
|
||||||
Key1 = 10,
|
Key1 = 10,
|
||||||
|
|
||||||
[Description("Key 2")]
|
[Description("Key 2")]
|
||||||
Key2,
|
Key2,
|
||||||
|
|
||||||
[Description("Key 3")]
|
[Description("Key 3")]
|
||||||
Key3,
|
Key3,
|
||||||
|
|
||||||
[Description("Key 4")]
|
[Description("Key 4")]
|
||||||
Key4,
|
Key4,
|
||||||
|
|
||||||
[Description("Key 5")]
|
[Description("Key 5")]
|
||||||
Key5,
|
Key5,
|
||||||
|
|
||||||
[Description("Key 6")]
|
[Description("Key 6")]
|
||||||
Key6,
|
Key6,
|
||||||
|
|
||||||
[Description("Key 7")]
|
[Description("Key 7")]
|
||||||
Key7,
|
Key7,
|
||||||
|
|
||||||
[Description("Key 8")]
|
[Description("Key 8")]
|
||||||
Key8,
|
Key8,
|
||||||
|
|
||||||
[Description("Key 9")]
|
[Description("Key 9")]
|
||||||
Key9,
|
Key9,
|
||||||
|
|
||||||
[Description("Key 10")]
|
[Description("Key 10")]
|
||||||
Key10,
|
Key10,
|
||||||
|
|
||||||
[Description("Key 11")]
|
[Description("Key 11")]
|
||||||
Key11,
|
Key11,
|
||||||
|
|
||||||
[Description("Key 12")]
|
[Description("Key 12")]
|
||||||
Key12,
|
Key12,
|
||||||
|
|
||||||
[Description("Key 13")]
|
[Description("Key 13")]
|
||||||
Key13,
|
Key13,
|
||||||
|
|
||||||
[Description("Key 14")]
|
[Description("Key 14")]
|
||||||
Key14,
|
Key14,
|
||||||
|
|
||||||
[Description("Key 15")]
|
[Description("Key 15")]
|
||||||
Key15,
|
Key15,
|
||||||
|
|
||||||
[Description("Key 16")]
|
[Description("Key 16")]
|
||||||
Key16,
|
Key16,
|
||||||
|
|
||||||
[Description("Key 17")]
|
[Description("Key 17")]
|
||||||
Key17,
|
Key17,
|
||||||
|
|
||||||
[Description("Key 18")]
|
[Description("Key 18")]
|
||||||
Key18,
|
Key18,
|
||||||
}
|
}
|
||||||
|
@ -330,12 +330,12 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
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)
|
if (columns % 2 == 1)
|
||||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||||
|
|
||||||
|
for (int i = 0; i < columns / 2; i++)
|
||||||
|
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||||
|
|
||||||
nextNormalAction = currentNormalAction;
|
nextNormalAction = currentNormalAction;
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
@ -349,6 +349,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
/// Number of columns in this stage lies at (item - Single).
|
/// Number of columns in this stage lies at (item - Single).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Single = 0,
|
Single = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Columns are grouped into two stages.
|
/// Columns are grouped into two stages.
|
||||||
/// Overall number of columns lies at (item - Dual), further computation is required for
|
/// Overall number of columns lies at (item - Dual), further computation is required for
|
||||||
|
@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
|
|||||||
/// <returns>The random value.</returns>
|
/// <returns>The random value.</returns>
|
||||||
public uint NextUInt()
|
public uint NextUInt()
|
||||||
{
|
{
|
||||||
uint t = X ^ X << 11;
|
uint t = X ^ (X << 11);
|
||||||
X = Y;
|
X = Y;
|
||||||
Y = Z;
|
Y = Z;
|
||||||
Z = W;
|
Z = W;
|
||||||
return W = W ^ W >> 19 ^ t ^ t >> 8;
|
return W = W ^ (W >> 19) ^ t ^ (t >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.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 Name => "Dual Stages";
|
||||||
public override string Acronym => "DS";
|
public override string Acronym => "DS";
|
||||||
@ -29,24 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
if (isForCurrentRuleset)
|
if (isForCurrentRuleset)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mbc.TargetColumns *= 2;
|
mbc.Dual = true;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayfieldType PlayfieldType => PlayfieldType.Dual;
|
public PlayfieldType PlayfieldType => PlayfieldType.Dual;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
@ -75,16 +76,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
AddNested(Tail);
|
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
|
public override Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return base.AccentColour; }
|
get => base.AccentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.AccentColour = value;
|
base.AccentColour = value;
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public override Color4 AccentColour
|
public override Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return base.AccentColour; }
|
get => base.AccentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.AccentColour = value;
|
base.AccentColour = value;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
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 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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -31,16 +32,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
InternalChild = headPiece = new NotePiece();
|
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
|
public override Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return base.AccentColour; }
|
get => base.AccentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.AccentColour = value;
|
base.AccentColour = value;
|
||||||
|
@ -77,11 +77,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return accentColour; }
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateAccentColour();
|
updateAccentColour();
|
||||||
@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
public bool Hitting
|
public bool Hitting
|
||||||
{
|
{
|
||||||
get { return hitting; }
|
get => hitting;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
hitting = value;
|
hitting = value;
|
||||||
|
@ -35,13 +35,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return accentColour; }
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateGlow();
|
updateGlow();
|
||||||
|
@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return Colour; }
|
get => Colour;
|
||||||
set { Colour = value; }
|
set => Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -49,20 +49,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
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);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return accentColour; }
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
colouredBox.Colour = AccentColour.Lighten(0.9f);
|
colouredBox.Colour = AccentColour.Lighten(0.9f);
|
||||||
|
@ -17,9 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
public double EndTime => StartTime + Duration;
|
public double EndTime => StartTime + Duration;
|
||||||
|
|
||||||
private double duration;
|
private double duration;
|
||||||
|
|
||||||
public double Duration
|
public double Duration
|
||||||
{
|
{
|
||||||
get { return duration; }
|
get => duration;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
duration = value;
|
duration = value;
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
public override double StartTime
|
public override double StartTime
|
||||||
{
|
{
|
||||||
get { return base.StartTime; }
|
get => base.StartTime;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.StartTime = value;
|
base.StartTime = value;
|
||||||
@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
public override int Column
|
public override int Column
|
||||||
{
|
{
|
||||||
get { return base.Column; }
|
get => base.Column;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.Column = value;
|
base.Column = value;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Types;
|
using osu.Game.Rulesets.Mania.Objects.Types;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
public virtual int Column
|
public virtual int Column
|
||||||
{
|
{
|
||||||
get => ColumnBindable;
|
get => ColumnBindable.Value;
|
||||||
set => ColumnBindable.Value = 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)>
|
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.Perfect, (44.8, 38.8, 27.8) },
|
||||||
{ HitResult.Great, (128, 98, 68 ) },
|
{ HitResult.Great, (128, 98, 68) },
|
||||||
{ HitResult.Good, (194, 164, 134) },
|
{ HitResult.Good, (194, 164, 134) },
|
||||||
{ HitResult.Ok, (254, 224, 194) },
|
{ HitResult.Ok, (254, 224, 194) },
|
||||||
{ HitResult.Meh, (302, 272, 242) },
|
{ 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:
|
@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
@ -82,28 +82,30 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
TopLevelContainer.Add(explosionContainer.CreateProxy());
|
TopLevelContainer.Add(explosionContainer.CreateProxy());
|
||||||
|
|
||||||
Direction.BindValueChanged(d =>
|
Direction.BindValueChanged(dir =>
|
||||||
{
|
{
|
||||||
hitTargetContainer.Padding = new MarginPadding
|
hitTargetContainer.Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
|
Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||||
Bottom = d == ScrollingDirection.Down ? 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);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Axes RelativeSizeAxes => Axes.Y;
|
public override Axes RelativeSizeAxes => Axes.Y;
|
||||||
|
|
||||||
private bool isSpecial;
|
private bool isSpecial;
|
||||||
|
|
||||||
public bool IsSpecial
|
public bool IsSpecial
|
||||||
{
|
{
|
||||||
get { return isSpecial; }
|
get => isSpecial;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (isSpecial == value)
|
if (isSpecial == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
isSpecial = value;
|
isSpecial = value;
|
||||||
|
|
||||||
Width = isSpecial ? special_column_width : column_width;
|
Width = isSpecial ? special_column_width : column_width;
|
||||||
@ -111,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get { return accentColour; }
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
background.AccentColour = value;
|
background.AccentColour = value;
|
||||||
@ -156,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements)
|
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
explosionContainer.Add(new HitExplosion(judgedObject)
|
explosionContainer.Add(new HitExplosion(judgedObject)
|
||||||
@ -167,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public bool OnPressed(ManiaAction action)
|
public bool OnPressed(ManiaAction action)
|
||||||
{
|
{
|
||||||
if (action != Action)
|
if (action != Action.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var nextObject =
|
var nextObject =
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
};
|
};
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
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();
|
updateColours();
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateColours();
|
updateColours();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
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;
|
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
|
||||||
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
||||||
@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateColours();
|
updateColours();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
};
|
};
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(direction =>
|
direction.BindValueChanged(dir =>
|
||||||
{
|
{
|
||||||
gradient.Colour = ColourInfo.GradientVertical(
|
gradient.Colour = ColourInfo.GradientVertical(
|
||||||
direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
|
dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
|
||||||
direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
|
dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
if (accentColour == value)
|
if (accentColour == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateColours();
|
updateColours();
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (JudgementText != null)
|
if (JudgementText != null)
|
||||||
JudgementText.TextSize = 25;
|
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
@ -15,7 +15,6 @@ using osu.Game.Input.Handlers;
|
|||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
BarLines.ForEach(Playfield.Add);
|
BarLines.ForEach(Playfield.Add);
|
||||||
|
|
||||||
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
|
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
|
||||||
configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
|
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||||
|
|
||||||
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
|
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
|
||||||
}
|
}
|
||||||
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
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);
|
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
||||||
|
|
||||||
|
@ -136,12 +136,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
AddColumn(column);
|
AddColumn(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
Direction.BindValueChanged(d =>
|
Direction.BindValueChanged(dir =>
|
||||||
{
|
{
|
||||||
barLineContainer.Padding = new MarginPadding
|
barLineContainer.Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
|
Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
|
||||||
Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
|
Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
|
||||||
};
|
};
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
if (!judgedObject.DisplayResult || !DisplayJudgements)
|
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
judgements.Clear();
|
judgements.Clear();
|
||||||
|
@ -33,9 +33,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
case Slider slider:
|
case Slider slider:
|
||||||
foreach (var nested in slider.NestedHitObjects)
|
foreach (var nested in slider.NestedHitObjects)
|
||||||
yield return createConvertValue(nested);
|
yield return createConvertValue(nested);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
yield return createConvertValue(hitObject);
|
yield return createConvertValue(hitObject);
|
||||||
|
|
||||||
break;
|
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;
|
private GameplayCursor cursor;
|
||||||
|
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new [] { typeof(CursorTrail) };
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
|
||||||
|
|
||||||
public CursorContainer Cursor => cursor;
|
public CursorContainer Cursor => cursor;
|
||||||
|
|
||||||
|
@ -89,7 +89,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private readonly bool auto;
|
private readonly bool auto;
|
||||||
|
|
||||||
public TestDrawableHitCircle(HitCircle h, bool auto) : base(h)
|
public TestDrawableHitCircle(HitCircle h, bool auto)
|
||||||
|
: base(h)
|
||||||
{
|
{
|
||||||
this.auto = auto;
|
this.auto = auto;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ using osuTK.Graphics;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -300,6 +301,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private float judgementOffsetDirection = 1;
|
private float judgementOffsetDirection = 1;
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
var osuObject = judgedObject as DrawableOsuHitObject;
|
var osuObject = judgedObject as DrawableOsuHitObject;
|
||||||
@ -313,7 +315,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = result.IsHit ? "Hit!" : "Miss!",
|
Text = result.IsHit ? "Hit!" : "Miss!",
|
||||||
Colour = result.IsHit ? Color4.Green : Color4.Red,
|
Colour = result.IsHit ? Color4.Green : Color4.Red,
|
||||||
TextSize = 30,
|
Font = OsuFont.GetFont(size: 30),
|
||||||
Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
|
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 class TestCaseSpinner : OsuTestCase
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(SpinnerDisc),
|
typeof(SpinnerDisc),
|
||||||
typeof(DrawableSpinner),
|
typeof(DrawableSpinner),
|
||||||
typeof(DrawableOsuHitObject)
|
typeof(DrawableOsuHitObject)
|
||||||
@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private bool auto;
|
private bool auto;
|
||||||
|
|
||||||
public TestDrawableSpinner(Spinner s, bool auto) : base(s)
|
public TestDrawableSpinner(Spinner s, bool auto)
|
||||||
|
: base(s)
|
||||||
{
|
{
|
||||||
this.auto = auto;
|
this.auto = auto;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||||
{
|
{
|
||||||
@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public double OverallDifficulty;
|
public double OverallDifficulty;
|
||||||
public int MaxCombo;
|
public int MaxCombo;
|
||||||
|
|
||||||
public OsuDifficultyAttributes(Mod[] mods, double starRating)
|
|
||||||
: base(mods, starRating)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||||
@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const int section_length = 400;
|
|
||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
|
|
||||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
@ -23,58 +25,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||||
{
|
{
|
||||||
if (!beatmap.HitObjects.Any())
|
if (beatmap.HitObjects.Count == 0)
|
||||||
return new OsuDifficultyAttributes(mods, 0);
|
return new OsuDifficultyAttributes { Mods = mods };
|
||||||
|
|
||||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
|
|
||||||
Skill[] skills =
|
|
||||||
{
|
|
||||||
new Aim(),
|
|
||||||
new Speed()
|
|
||||||
};
|
|
||||||
|
|
||||||
double sectionLength = section_length * timeRate;
|
|
||||||
|
|
||||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
|
||||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
|
||||||
|
|
||||||
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
|
||||||
{
|
|
||||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
|
||||||
{
|
|
||||||
foreach (Skill s in skills)
|
|
||||||
{
|
|
||||||
s.SaveCurrentPeak();
|
|
||||||
s.StartNewSectionFrom(currentSectionEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSectionEnd += sectionLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Skill s in skills)
|
|
||||||
s.Process(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The peak strain will not be saved for the last section in the above loop
|
|
||||||
foreach (Skill s in skills)
|
|
||||||
s.SaveCurrentPeak();
|
|
||||||
|
|
||||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||||
|
|
||||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
|
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
|
||||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
|
|
||||||
int maxCombo = beatmap.HitObjects.Count;
|
int maxCombo = beatmap.HitObjects.Count;
|
||||||
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
||||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||||
|
|
||||||
return new OsuDifficultyAttributes(mods, starRating)
|
return new OsuDifficultyAttributes
|
||||||
{
|
{
|
||||||
|
StarRating = starRating,
|
||||||
|
Mods = mods,
|
||||||
AimStrain = aimRating,
|
AimStrain = aimRating,
|
||||||
SpeedStrain = speedRating,
|
SpeedStrain = speedRating,
|
||||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||||
@ -83,6 +54,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
|
{
|
||||||
|
// The first jump is formed by the first two hitobjects of the map.
|
||||||
|
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
||||||
|
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null;
|
||||||
|
var last = beatmap.HitObjects[i - 1];
|
||||||
|
var current = beatmap.HitObjects[i];
|
||||||
|
|
||||||
|
yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||||
|
{
|
||||||
|
new Aim(),
|
||||||
|
new Speed()
|
||||||
|
};
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||||
{
|
{
|
||||||
new OsuModDoubleTime(),
|
new OsuModDoubleTime(),
|
||||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
||||||
|
|
||||||
aimValue *= lengthBonus;
|
aimValue *= lengthBonus;
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
|
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
|
||||||
else if (Attributes.ApproachRate < 8.0f)
|
else if (Attributes.ApproachRate < 8.0f)
|
||||||
{
|
{
|
||||||
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
|
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
aimValue *= approachRateFactor;
|
aimValue *= approachRateFactor;
|
||||||
@ -126,8 +126,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
// Apply object-based bonus for flashlight.
|
// Apply object-based bonus for flashlight.
|
||||||
aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
|
aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
|
||||||
(totalHits > 200 ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
|
(totalHits > 200
|
||||||
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) : 0.0f);
|
? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
|
||||||
|
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f)
|
||||||
|
: 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the aim value with accuracy _slightly_
|
// Scale the aim value with accuracy _slightly_
|
||||||
@ -144,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
speedValue *= Math.Pow(0.97f, countMiss);
|
speedValue *= Math.Pow(0.97f, countMiss);
|
||||||
|
@ -1,50 +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.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
|
|
||||||
/// which contains extra data required for difficulty calculation.
|
|
||||||
/// </summary>
|
|
||||||
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
|
|
||||||
{
|
|
||||||
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
|
|
||||||
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
|
|
||||||
/// </summary>
|
|
||||||
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
|
|
||||||
{
|
|
||||||
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
|
||||||
// This should probably happen before the objects reach the difficulty calculator.
|
|
||||||
difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerator<OsuDifficultyHitObject> GetEnumerator() => difficultyObjects;
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
|
|
||||||
{
|
|
||||||
// The first jump is formed by the first two hitobjects of the map.
|
|
||||||
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
|
||||||
for (int i = 1; i < objects.Count; i++)
|
|
||||||
{
|
|
||||||
var lastLast = i > 1 ? objects[i - 2] : null;
|
|
||||||
var last = objects[i - 1];
|
|
||||||
var current = objects[i];
|
|
||||||
|
|
||||||
yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +1,20 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||||
{
|
{
|
||||||
/// <summary>
|
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||||
/// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
|
|
||||||
/// </summary>
|
|
||||||
public class OsuDifficultyHitObject
|
|
||||||
{
|
{
|
||||||
private const int normalized_radius = 52;
|
private const int normalized_radius = 52;
|
||||||
|
|
||||||
/// <summary>
|
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||||
/// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
|
|
||||||
/// </summary>
|
|
||||||
public OsuHitObject BaseObject { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||||
@ -30,40 +26,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double TravelDistance { get; private set; }
|
public double TravelDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds elapsed since the StartTime of the previous <see cref="OsuDifficultyHitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
public double DeltaTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
|
||||||
/// </summary>
|
|
||||||
public double StrainTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
||||||
/// Calculated as the angle between the circles (current-2, current-1, current).
|
/// Calculated as the angle between the circles (current-2, current-1, current).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Angle { get; private set; }
|
public double? Angle { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
||||||
|
/// </summary>
|
||||||
|
public readonly double StrainTime;
|
||||||
|
|
||||||
private readonly OsuHitObject lastLastObject;
|
private readonly OsuHitObject lastLastObject;
|
||||||
private readonly OsuHitObject lastObject;
|
private readonly OsuHitObject lastObject;
|
||||||
private readonly double timeRate;
|
|
||||||
|
|
||||||
/// <summary>
|
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate)
|
||||||
/// Initializes the object calculating extra data required for difficulty calculation.
|
: base(hitObject, lastObject, clockRate)
|
||||||
/// </summary>
|
|
||||||
public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate)
|
|
||||||
{
|
{
|
||||||
this.lastLastObject = lastLastObject;
|
this.lastLastObject = (OsuHitObject)lastLastObject;
|
||||||
this.lastObject = lastObject;
|
this.lastObject = (OsuHitObject)lastObject;
|
||||||
this.timeRate = timeRate;
|
|
||||||
|
|
||||||
BaseObject = currentObject;
|
|
||||||
|
|
||||||
setDistances();
|
setDistances();
|
||||||
setTimingValues();
|
|
||||||
// Calculate angle here
|
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||||
|
StrainTime = Math.Max(50, DeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDistances()
|
private void setDistances()
|
||||||
@ -102,18 +88,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTimingValues()
|
|
||||||
{
|
|
||||||
DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
|
|
||||||
|
|
||||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
|
||||||
StrainTime = Math.Max(50, DeltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeSliderCursorPosition(Slider slider)
|
private void computeSliderCursorPosition(Slider slider)
|
||||||
{
|
{
|
||||||
if (slider.LazyEndPosition != null)
|
if (slider.LazyEndPosition != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
slider.LazyEndPosition = slider.StackedPosition;
|
slider.LazyEndPosition = slider.StackedPosition;
|
||||||
|
|
||||||
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
||||||
@ -149,8 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
Vector2 pos = hitObject.StackedPosition;
|
Vector2 pos = hitObject.StackedPosition;
|
||||||
|
|
||||||
var slider = hitObject as Slider;
|
if (hitObject is Slider slider)
|
||||||
if (slider != null)
|
|
||||||
{
|
{
|
||||||
computeSliderCursorPosition(slider);
|
computeSliderCursorPosition(slider);
|
||||||
pos = slider.LazyEndPosition ?? pos;
|
pos = slider.LazyEndPosition ?? pos;
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -17,33 +20,40 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
protected override double SkillMultiplier => 26.25;
|
protected override double SkillMultiplier => 26.25;
|
||||||
protected override double StrainDecayBase => 0.15;
|
protected override double StrainDecayBase => 0.15;
|
||||||
|
|
||||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
|
if (current.BaseObject is Spinner)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||||
|
|
||||||
double result = 0;
|
double result = 0;
|
||||||
|
|
||||||
const double scale = 90;
|
|
||||||
|
|
||||||
double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
|
||||||
|
|
||||||
if (Previous.Count > 0)
|
if (Previous.Count > 0)
|
||||||
{
|
{
|
||||||
if (current.Angle != null && current.Angle.Value > angle_bonus_begin)
|
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
|
||||||
|
|
||||||
|
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
|
||||||
{
|
{
|
||||||
|
const double scale = 90;
|
||||||
|
|
||||||
var angleBonus = Math.Sqrt(
|
var angleBonus = Math.Sqrt(
|
||||||
Math.Max(Previous[0].JumpDistance - scale, 0)
|
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
||||||
* Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2)
|
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
||||||
* Math.Max(current.JumpDistance - scale, 0));
|
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
||||||
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime);
|
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double jumpDistanceExp = applyDiminishingExp(current.JumpDistance);
|
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
|
||||||
double travelDistanceExp = applyDiminishingExp(current.TravelDistance);
|
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
||||||
|
|
||||||
return Math.Max(
|
return Math.Max(
|
||||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold),
|
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
||||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime
|
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
@ -11,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Speed : Skill
|
public class Speed : Skill
|
||||||
{
|
{
|
||||||
|
private const double single_spacing_threshold = 125;
|
||||||
|
|
||||||
private const double angle_bonus_begin = 5 * Math.PI / 6;
|
private const double angle_bonus_begin = 5 * Math.PI / 6;
|
||||||
private const double pi_over_4 = Math.PI / 4;
|
private const double pi_over_4 = Math.PI / 4;
|
||||||
private const double pi_over_2 = Math.PI / 2;
|
private const double pi_over_2 = Math.PI / 2;
|
||||||
@ -22,9 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
private const double max_speed_bonus = 45; // ~330BPM
|
private const double max_speed_bonus = 45; // ~330BPM
|
||||||
private const double speed_balancing_factor = 40;
|
private const double speed_balancing_factor = 40;
|
||||||
|
|
||||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance);
|
if (current.BaseObject is Spinner)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||||
|
|
||||||
|
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
|
||||||
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
|
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
|
||||||
|
|
||||||
double speedBonus = 1.0;
|
double speedBonus = 1.0;
|
||||||
@ -32,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
|
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
|
||||||
|
|
||||||
double angleBonus = 1.0;
|
double angleBonus = 1.0;
|
||||||
if (current.Angle != null && current.Angle.Value < angle_bonus_begin)
|
if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin)
|
||||||
{
|
{
|
||||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57;
|
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57;
|
||||||
if (current.Angle.Value < pi_over_2)
|
if (osuCurrent.Angle.Value < pi_over_2)
|
||||||
{
|
{
|
||||||
angleBonus = 1.28;
|
angleBonus = 1.28;
|
||||||
if (distance < 90 && current.Angle.Value < pi_over_4)
|
if (distance < 90 && osuCurrent.Angle.Value < pi_over_4)
|
||||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
|
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
|
||||||
else if (distance < 90)
|
else if (distance < 90)
|
||||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4);
|
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime;
|
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
|
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
|
||||||
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
|
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
|
||||||
ScaleBindable.BindValueChanged(v => Scale = new Vector2(v), true);
|
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
|
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
body.BorderColour = colours.Yellow;
|
body.BorderColour = colours.Yellow;
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
||||||
ScaleBindable.BindValueChanged(v => body.PathWidth = v * 64, true);
|
ScaleBindable.BindValueChanged(scale => body.PathWidth = scale.NewValue * 64, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePosition() => Position = slider.StackedPosition;
|
private void updatePosition() => Position = slider.StackedPosition;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
|||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
||||||
StackHeightBindable.BindValueChanged(_ => updatePosition());
|
StackHeightBindable.BindValueChanged(_ => updatePosition());
|
||||||
ScaleBindable.BindValueChanged(v => ring.Scale = new Vector2(v), true);
|
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePosition() => Position = spinner.Position;
|
private void updatePosition() => Position = spinner.Position;
|
||||||
|
@ -9,8 +9,10 @@ namespace osu.Game.Rulesets.Osu.Judgements
|
|||||||
{
|
{
|
||||||
[Description(@"")]
|
[Description(@"")]
|
||||||
None,
|
None,
|
||||||
|
|
||||||
[Description(@"Good")]
|
[Description(@"Good")]
|
||||||
Good,
|
Good,
|
||||||
|
|
||||||
[Description(@"Amazing")]
|
[Description(@"Amazing")]
|
||||||
Perfect
|
Perfect
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
scoreProcessor.Health.ValueChanged += val => { blinds.AnimateClosedness((float)val); };
|
scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
return default_flashlight_size;
|
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";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
74
osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
Normal file
74
osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
|
||||||
|
{
|
||||||
|
public override string Name => "Grow";
|
||||||
|
|
||||||
|
public override string Acronym => "GR";
|
||||||
|
|
||||||
|
public override FontAwesome Icon => FontAwesome.fa_arrows_v;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
|
public override string Description => "Hit them at the right size!";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
|
{
|
||||||
|
foreach (var drawable in drawables)
|
||||||
|
{
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableSpinner _:
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
drawable.ApplyCustomUpdateState += ApplyCustomState;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
|
||||||
|
{
|
||||||
|
var h = (OsuHitObject)drawable.HitObject;
|
||||||
|
|
||||||
|
// apply grow effect
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableSliderHead _:
|
||||||
|
case DrawableSliderTail _:
|
||||||
|
// special cases we should *not* be scaling.
|
||||||
|
break;
|
||||||
|
case DrawableSlider _:
|
||||||
|
case DrawableHitCircle _:
|
||||||
|
{
|
||||||
|
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||||
|
drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove approach circles
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
// we don't want to see the approach circle
|
||||||
|
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||||
|
circle.ApproachCircle.Hide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
foreach (var drawable in drawables)
|
foreach (var drawable in drawables)
|
||||||
{
|
{
|
||||||
var hitObject = (OsuHitObject) drawable.HitObject;
|
var hitObject = (OsuHitObject)drawable.HitObject;
|
||||||
|
|
||||||
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
|
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
.MoveTo(originalPosition, moveDuration, Easing.InOutSine);
|
.MoveTo(originalPosition, moveDuration, Easing.InOutSine);
|
||||||
}
|
}
|
||||||
|
|
||||||
theta += (float) hitObject.TimeFadeIn / 1000;
|
theta += (float)hitObject.TimeFadeIn / 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connects hit objects visually, for example with follow points.
|
/// Connects hit objects visually, for example with follow points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ConnectionRenderer<T> : Container
|
public abstract class ConnectionRenderer<T> : LifetimeManagementContainer
|
||||||
where T : HitObject
|
where T : HitObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -12,39 +12,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
public class FollowPointRenderer : ConnectionRenderer<OsuHitObject>
|
public class FollowPointRenderer : ConnectionRenderer<OsuHitObject>
|
||||||
{
|
{
|
||||||
private int pointDistance = 32;
|
private int pointDistance = 32;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines how much space there is between points.
|
/// Determines how much space there is between points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PointDistance
|
public int PointDistance
|
||||||
{
|
{
|
||||||
get { return pointDistance; }
|
get => pointDistance;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (pointDistance == value) return;
|
if (pointDistance == value) return;
|
||||||
|
|
||||||
pointDistance = value;
|
pointDistance = value;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int preEmpt = 800;
|
private int preEmpt = 800;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
|
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PreEmpt
|
public int PreEmpt
|
||||||
{
|
{
|
||||||
get { return preEmpt; }
|
get => preEmpt;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (preEmpt == value) return;
|
if (preEmpt == value) return;
|
||||||
|
|
||||||
preEmpt = value;
|
preEmpt = value;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<OsuHitObject> hitObjects;
|
private IEnumerable<OsuHitObject> hitObjects;
|
||||||
|
|
||||||
public override IEnumerable<OsuHitObject> HitObjects
|
public override IEnumerable<OsuHitObject> HitObjects
|
||||||
{
|
{
|
||||||
get { return hitObjects; }
|
get => hitObjects;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
hitObjects = value;
|
hitObjects = value;
|
||||||
@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
|
|
||||||
private void update()
|
private void update()
|
||||||
{
|
{
|
||||||
Clear();
|
ClearInternal();
|
||||||
|
|
||||||
if (hitObjects == null)
|
if (hitObjects == null)
|
||||||
return;
|
return;
|
||||||
@ -86,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
|
|
||||||
FollowPoint fp;
|
FollowPoint fp;
|
||||||
|
|
||||||
Add(fp = new FollowPoint
|
AddInternal(fp = new FollowPoint
|
||||||
{
|
{
|
||||||
Position = pointStartPosition,
|
Position = pointStartPosition,
|
||||||
Rotation = rotation,
|
Rotation = rotation,
|
||||||
@ -107,6 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
fp.Expire(true);
|
fp.Expire(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prevHitObject = currHitObject;
|
prevHitObject = currHitObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user