diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3c52802cf6..ec3816d541 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,10 +20,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- - name: Install .NET 5.0.x
+ - name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
with:
- dotnet-version: "5.0.x"
+ dotnet-version: "6.0.x"
# FIXME: libavformat is not included in Ubuntu. Let's fix that.
# https://github.com/ppy/osu-framework/issues/4349
@@ -65,10 +65,10 @@ jobs:
run: |
$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2
- - name: Install .NET 5.0.x
+ - name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
with:
- dotnet-version: "5.0.x"
+ dotnet-version: "6.0.x"
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
# cannot accept .sln(f) files as arguments.
@@ -84,10 +84,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- - name: Install .NET 5.0.x
+ - name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
with:
- dotnet-version: "5.0.x"
+ dotnet-version: "6.0.x"
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
# cannot accept .sln(f) files as arguments.
@@ -102,17 +102,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side.
+ # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
- name: Install .NET 3.1.x LTS
uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- - name: Install .NET 5.0.x
+ - name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
with:
- dotnet-version: "5.0.x"
+ dotnet-version: "6.0.x"
- name: Restore Tools
run: dotnet tool restore
diff --git a/.gitignore b/.gitignore
index de6a3ac848..5b19270ab9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -339,3 +339,4 @@ inspectcode
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
+**/FodyWeavers.xml
diff --git a/.run/osu! (Second Client).run.xml b/.run/osu! (Second Client).run.xml
index 599b4b986b..9a471df902 100644
--- a/.run/osu! (Second Client).run.xml
+++ b/.run/osu! (Second Client).run.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,9 +12,9 @@
-
+
-
\ No newline at end of file
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 894ea25c8b..c1682638c2 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,7 @@
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
@@ -32,13 +32,8 @@
NU1701:
DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
This is required due to https://github.com/NuGet/Home/issues/5740
-
- CA9998:
- Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated.
- The entire package will be able to be removed after migrating to .NET 5,
- as analysers are shipped as part of the .NET 5 SDK anyway.
-->
- $(NoWarn);NU1701;CA9998
+ $(NoWarn);NU1701
false
diff --git a/Gemfile.lock b/Gemfile.lock
index 8ac863c9a8..1010027af9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,58 +1,80 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.3)
- addressable (2.7.0)
+ CFPropertyList (3.0.5)
+ rexml
+ addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
+ artifactory (3.0.15)
atomos (0.1.3)
- aws-eventstream (1.1.0)
- aws-partitions (1.413.0)
- aws-sdk-core (3.110.0)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.553.0)
+ aws-sdk-core (3.126.0)
aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.239.0)
+ aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.40.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-kms (1.54.0)
+ aws-sdk-core (~> 3, >= 3.126.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.87.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-s3 (1.112.0)
+ aws-sdk-core (~> 3, >= 3.126.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.2)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
- claide (1.0.3)
+ claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
- commander-fastlane (4.4.6)
- highline (~> 1.7.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
declarative (0.0.20)
- declarative-option (0.1.0)
- digest-crc (0.6.3)
+ digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
- emoji_regex (3.2.1)
- excon (0.78.1)
- faraday (1.2.0)
- multipart-post (>= 1.2, < 3)
- ruby2_keywords
+ emoji_regex (3.2.3)
+ excon (0.91.0)
+ faraday (1.9.3)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
- faraday_middleware (1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.0.3)
+ multipart-post (>= 1.2, < 3)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
+ faraday_middleware (1.2.0)
faraday (~> 1.0)
- fastimage (2.2.1)
- fastlane (2.170.0)
+ fastimage (2.2.6)
+ fastlane (2.204.2)
CFPropertyList (>= 2.3, < 4.0.0)
- addressable (>= 2.3, < 3.0.0)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
- commander-fastlane (>= 4.4.6, < 5.0.0)
+ commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
@@ -61,18 +83,20 @@ GEM
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
- google-api-client (>= 0.37.0, < 0.39.0)
- google-cloud-storage (>= 1.15.0, < 2.0.0)
- highline (>= 1.7.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
+ optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
- slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
@@ -82,84 +106,98 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-clean_testflight_testers (0.3.0)
- fastlane-plugin-souyuz (0.9.1)
- souyuz (= 0.9.1)
+ fastlane-plugin-souyuz (0.11.1)
+ souyuz (= 0.11.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-api-client (0.38.0)
+ google-apis-androidpublisher_v3 (0.16.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
- googleauth (~> 0.9)
- httpclient (>= 2.8.1, < 3.0)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
- retriable (>= 2.0, < 4.0)
- signet (~> 0.12)
- google-cloud-core (1.5.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.10.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-playcustomapp_v1 (0.7.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.11.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.4.0)
+ google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
- google-cloud-errors (1.0.1)
- google-cloud-storage (1.29.2)
- addressable (~> 2.5)
+ google-cloud-errors (1.2.0)
+ google-cloud-storage (1.36.0)
+ addressable (~> 2.8)
digest-crc (~> 0.4)
- google-api-client (~> 0.33)
- google-cloud-core (~> 1.2)
- googleauth (~> 0.9)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (0.14.0)
+ googleauth (1.1.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
- signet (~> 0.14)
- highline (1.7.10)
- http-cookie (1.0.3)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
- jmespath (1.4.0)
- json (2.5.1)
- jwt (2.2.2)
+ jmespath (1.5.0)
+ json (2.6.1)
+ jwt (2.3.0)
memoist (0.16.2)
mini_magick (4.11.0)
- mini_mime (1.0.2)
- mini_portile2 (2.4.0)
+ mini_mime (1.1.2)
+ mini_portile2 (2.7.1)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
- naturally (2.2.0)
- nokogiri (1.10.10)
- mini_portile2 (~> 2.4.0)
- os (1.1.1)
- plist (3.5.0)
+ naturally (2.2.1)
+ nokogiri (1.13.1)
+ mini_portile2 (~> 2.7.0)
+ racc (~> 1.4)
+ optparse (0.1.1)
+ os (1.1.4)
+ plist (3.6.0)
public_suffix (4.0.6)
- rake (13.0.3)
- representable (3.0.4)
+ racc (1.6.0)
+ rake (13.0.6)
+ representable (3.1.1)
declarative (< 0.1.0)
- declarative-option (< 0.2.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
+ rexml (3.2.5)
rouge (2.0.7)
- ruby2_keywords (0.0.2)
- rubyzip (2.3.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.3.2)
security (0.1.3)
- signet (0.14.0)
- addressable (~> 2.3)
+ signet (0.16.0)
+ addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
- slack-notifier (2.3.2)
- souyuz (0.9.1)
- fastlane (>= 1.103.0)
- highline (~> 1.7)
+ souyuz (0.11.1)
+ fastlane (>= 2.182.0)
+ highline (~> 2.0)
nokogiri (~> 1.7)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@@ -167,18 +205,20 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.7)
- unicode-display_width (1.7.0)
+ unf_ext (0.0.8)
+ unicode-display_width (1.8.0)
+ webrick (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.19.0)
+ xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
- xcpretty-travis-formatter (1.0.0)
+ xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
diff --git a/README.md b/README.md
index b1dfcab416..7ace47a74f 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
Please make sure you have the following prerequisites:
-- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed.
+- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig
index f3badda9b3..9c7537de4b 100644
--- a/Templates/Rulesets/ruleset-empty/.editorconfig
+++ b/Templates/Rulesets/ruleset-empty/.editorconfig
@@ -10,14 +10,6 @@ trim_trailing_whitespace = true
#Roslyn naming styles
-#PascalCase for public and protected members
-dotnet_naming_style.pascalcase.capitalization = pascal_case
-dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
-dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
-dotnet_naming_rule.public_members_pascalcase.severity = error
-dotnet_naming_rule.public_members_pascalcase.symbols = public_members
-dotnet_naming_rule.public_members_pascalcase.style = pascalcase
-
#camelCase for private members
dotnet_naming_style.camelcase.capitalization = camel_case
@@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
-csharp_style_var_for_built_in_types = true:none
+csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
@@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
-csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_index_operator = false:silent
+csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
@@ -197,4 +189,4 @@ dotnet_diagnostic.IDE0069.severity = none
dotnet_diagnostic.CA2225.severity = none
# Banned APIs
-dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
+dotnet_diagnostic.RS0030.severity = error
diff --git a/Templates/Rulesets/ruleset-empty/.gitignore b/Templates/Rulesets/ruleset-empty/.gitignore
index 940794e60f..5b19270ab9 100644
--- a/Templates/Rulesets/ruleset-empty/.gitignore
+++ b/Templates/Rulesets/ruleset-empty/.gitignore
@@ -1,7 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
@@ -17,8 +15,6 @@
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
-x64/
-x86/
bld/
[Bb]in/
[Oo]bj/
@@ -42,11 +38,10 @@ TestResult.xml
[Rr]eleasePS/
dlldata.c
-# .NET Core
+# DNX
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
*_i.c
*_p.c
@@ -113,10 +108,6 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
# NCrunch
_NCrunch_*
.*crunch*.local.xml
@@ -166,7 +157,7 @@ PublishScripts/
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
+# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
@@ -196,10 +187,11 @@ ClientBin/
*~
*.dbmdl
*.dbproj.schemaview
-*.jfm
*.pfx
*.publishsettings
+node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@@ -219,7 +211,6 @@ UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
-*.ndf
# Business Intelligence projects
*.rdl.data
@@ -234,10 +225,6 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
# Visual Studio 6 build log
*.plg
@@ -245,9 +232,6 @@ typings/
# Visual Studio 6 workspace options file
*.opt
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -263,26 +247,96 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
+# Cake #
+/tools/**
+/build/tools/**
+/build/temp/**
-# Telerik's JustMock configuration file
-*.jmconfig
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# fastlane
+fastlane/report.xml
+
+# inspectcode
+inspectcodereport.xml
+inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
+
+*.GeneratedMSBuildEditorConfig.editorconfig
+
+# Fody (pulled in by Realm) - schema file
+FodyWeavers.xsd
+**/FodyWeavers.xml
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
index 4f810ce17f..03ee7c9204 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index 3c6aaa39ca..cb922c5a58 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -20,7 +20,7 @@
WinExe
- net5.0
+ net6.0
osu.Game.Rulesets.EmptyFreeform.Tests
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings
index aa8f8739c1..9752e08599 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings
@@ -18,9 +18,10 @@
WARNING
HINT
DO_NOT_SHOW
- HINT
- WARNING
- WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
WARNING
WARNING
WARNING
@@ -73,6 +74,7 @@
HINT
WARNING
HINT
+ DO_NOT_SHOW
WARNING
DO_NOT_SHOW
WARNING
@@ -105,8 +107,9 @@
HINT
HINT
WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
WARNING
- DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -120,6 +123,7 @@
WARNING
WARNING
HINT
+ HINT
WARNING
HINT
HINT
@@ -129,7 +133,7 @@
HINT
WARNING
WARNING
- HINT
+ WARNING
WARNING
WARNING
WARNING
@@ -204,8 +208,10 @@
HINT
WARNING
WARNING
- DO_NOT_SHOW
+ SUGGESTION
DO_NOT_SHOW
+
+ True
DO_NOT_SHOW
WARNING
WARNING
@@ -226,6 +232,7 @@
HINT
DO_NOT_SHOW
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -298,15 +305,21 @@
True
200
CHOP_IF_LONG
+ UseExplicitType
+ UseVarWhenEvident
+ UseVarWhenEvident
False
False
AABB
API
BPM
+ EF
+ FPS
GC
GL
GLSL
HID
+ HSV
HTML
HUD
ID
@@ -717,9 +730,6 @@
</Group>
</TypePattern>
</Patterns>
- 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.
-
<Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
@@ -909,26 +919,82 @@ private void load()
{
$END$
};
+ True
True
True
+ True
True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
- True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
index cc4483de31..a9bc8dc10e 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
index cfe2bd1cb2..092a013614 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
@@ -1,7 +1,7 @@
netstandard2.1
- osu.Game.Rulesets.Sample
+ osu.Game.Rulesets.EmptyFreeform
Library
AnyCPU
osu.Game.Rulesets.EmptyFreeform
diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig
index f3badda9b3..9c7537de4b 100644
--- a/Templates/Rulesets/ruleset-example/.editorconfig
+++ b/Templates/Rulesets/ruleset-example/.editorconfig
@@ -10,14 +10,6 @@ trim_trailing_whitespace = true
#Roslyn naming styles
-#PascalCase for public and protected members
-dotnet_naming_style.pascalcase.capitalization = pascal_case
-dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
-dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
-dotnet_naming_rule.public_members_pascalcase.severity = error
-dotnet_naming_rule.public_members_pascalcase.symbols = public_members
-dotnet_naming_rule.public_members_pascalcase.style = pascalcase
-
#camelCase for private members
dotnet_naming_style.camelcase.capitalization = camel_case
@@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
-csharp_style_var_for_built_in_types = true:none
+csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
@@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
-csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_index_operator = false:silent
+csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
@@ -197,4 +189,4 @@ dotnet_diagnostic.IDE0069.severity = none
dotnet_diagnostic.CA2225.severity = none
# Banned APIs
-dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
+dotnet_diagnostic.RS0030.severity = error
diff --git a/Templates/Rulesets/ruleset-example/.gitignore b/Templates/Rulesets/ruleset-example/.gitignore
index 940794e60f..5b19270ab9 100644
--- a/Templates/Rulesets/ruleset-example/.gitignore
+++ b/Templates/Rulesets/ruleset-example/.gitignore
@@ -1,7 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
@@ -17,8 +15,6 @@
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
-x64/
-x86/
bld/
[Bb]in/
[Oo]bj/
@@ -42,11 +38,10 @@ TestResult.xml
[Rr]eleasePS/
dlldata.c
-# .NET Core
+# DNX
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
*_i.c
*_p.c
@@ -113,10 +108,6 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
# NCrunch
_NCrunch_*
.*crunch*.local.xml
@@ -166,7 +157,7 @@ PublishScripts/
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
+# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
@@ -196,10 +187,11 @@ ClientBin/
*~
*.dbmdl
*.dbproj.schemaview
-*.jfm
*.pfx
*.publishsettings
+node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@@ -219,7 +211,6 @@ UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
-*.ndf
# Business Intelligence projects
*.rdl.data
@@ -234,10 +225,6 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
# Visual Studio 6 build log
*.plg
@@ -245,9 +232,6 @@ typings/
# Visual Studio 6 workspace options file
*.opt
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -263,26 +247,96 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
+# Cake #
+/tools/**
+/build/tools/**
+/build/temp/**
-# Telerik's JustMock configuration file
-*.jmconfig
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# fastlane
+fastlane/report.xml
+
+# inspectcode
+inspectcodereport.xml
+inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
+
+*.GeneratedMSBuildEditorConfig.editorconfig
+
+# Fody (pulled in by Realm) - schema file
+FodyWeavers.xsd
+**/FodyWeavers.xml
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 0719dd30df..5ecd9cc675 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -20,7 +20,7 @@
WinExe
- net5.0
+ net6.0
osu.Game.Rulesets.Pippidon.Tests
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
index aa8f8739c1..9752e08599 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
@@ -18,9 +18,10 @@
WARNING
HINT
DO_NOT_SHOW
- HINT
- WARNING
- WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
WARNING
WARNING
WARNING
@@ -73,6 +74,7 @@
HINT
WARNING
HINT
+ DO_NOT_SHOW
WARNING
DO_NOT_SHOW
WARNING
@@ -105,8 +107,9 @@
HINT
HINT
WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
WARNING
- DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -120,6 +123,7 @@
WARNING
WARNING
HINT
+ HINT
WARNING
HINT
HINT
@@ -129,7 +133,7 @@
HINT
WARNING
WARNING
- HINT
+ WARNING
WARNING
WARNING
WARNING
@@ -204,8 +208,10 @@
HINT
WARNING
WARNING
- DO_NOT_SHOW
+ SUGGESTION
DO_NOT_SHOW
+
+ True
DO_NOT_SHOW
WARNING
WARNING
@@ -226,6 +232,7 @@
HINT
DO_NOT_SHOW
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -298,15 +305,21 @@
True
200
CHOP_IF_LONG
+ UseExplicitType
+ UseVarWhenEvident
+ UseVarWhenEvident
False
False
AABB
API
BPM
+ EF
+ FPS
GC
GL
GLSL
HID
+ HSV
HTML
HUD
ID
@@ -717,9 +730,6 @@
</Group>
</TypePattern>
</Patterns>
- 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.
-
<Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
@@ -909,26 +919,82 @@ private void load()
{
$END$
};
+ True
True
True
+ True
True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
- True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
index e005346e1e..dbfaf8a01d 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => true;
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
index 61b859f45b..a3607343c9 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -1,7 +1,7 @@
netstandard2.1
- osu.Game.Rulesets.Sample
+ osu.Game.Rulesets.Pippidon
Library
AnyCPU
osu.Game.Rulesets.Pippidon
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig
index f3badda9b3..9c7537de4b 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig
+++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig
@@ -10,14 +10,6 @@ trim_trailing_whitespace = true
#Roslyn naming styles
-#PascalCase for public and protected members
-dotnet_naming_style.pascalcase.capitalization = pascal_case
-dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
-dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
-dotnet_naming_rule.public_members_pascalcase.severity = error
-dotnet_naming_rule.public_members_pascalcase.symbols = public_members
-dotnet_naming_rule.public_members_pascalcase.style = pascalcase
-
#camelCase for private members
dotnet_naming_style.camelcase.capitalization = camel_case
@@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
-csharp_style_var_for_built_in_types = true:none
+csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
@@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
-csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_index_operator = false:silent
+csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
@@ -197,4 +189,4 @@ dotnet_diagnostic.IDE0069.severity = none
dotnet_diagnostic.CA2225.severity = none
# Banned APIs
-dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
+dotnet_diagnostic.RS0030.severity = error
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore
index 940794e60f..5b19270ab9 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore
+++ b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore
@@ -1,7 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
@@ -17,8 +15,6 @@
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
-x64/
-x86/
bld/
[Bb]in/
[Oo]bj/
@@ -42,11 +38,10 @@ TestResult.xml
[Rr]eleasePS/
dlldata.c
-# .NET Core
+# DNX
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
*_i.c
*_p.c
@@ -113,10 +108,6 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
# NCrunch
_NCrunch_*
.*crunch*.local.xml
@@ -166,7 +157,7 @@ PublishScripts/
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
+# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
@@ -196,10 +187,11 @@ ClientBin/
*~
*.dbmdl
*.dbproj.schemaview
-*.jfm
*.pfx
*.publishsettings
+node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@@ -219,7 +211,6 @@ UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
-*.ndf
# Business Intelligence projects
*.rdl.data
@@ -234,10 +225,6 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
# Visual Studio 6 build log
*.plg
@@ -245,9 +232,6 @@ typings/
# Visual Studio 6 workspace options file
*.opt
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -263,26 +247,96 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
+# Cake #
+/tools/**
+/build/tools/**
+/build/temp/**
-# Telerik's JustMock configuration file
-*.jmconfig
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# fastlane
+fastlane/report.xml
+
+# inspectcode
+inspectcodereport.xml
+inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
+
+*.GeneratedMSBuildEditorConfig.editorconfig
+
+# Fody (pulled in by Realm) - schema file
+FodyWeavers.xsd
+**/FodyWeavers.xml
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
index 65cfb2bff4..b45505678c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index d0db43cc81..33ad0ac4f7 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -20,7 +20,7 @@
WinExe
- net5.0
+ net6.0
osu.Game.Rulesets.EmptyScrolling.Tests
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings
index aa8f8739c1..9752e08599 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings
@@ -18,9 +18,10 @@
WARNING
HINT
DO_NOT_SHOW
- HINT
- WARNING
- WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
WARNING
WARNING
WARNING
@@ -73,6 +74,7 @@
HINT
WARNING
HINT
+ DO_NOT_SHOW
WARNING
DO_NOT_SHOW
WARNING
@@ -105,8 +107,9 @@
HINT
HINT
WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
WARNING
- DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -120,6 +123,7 @@
WARNING
WARNING
HINT
+ HINT
WARNING
HINT
HINT
@@ -129,7 +133,7 @@
HINT
WARNING
WARNING
- HINT
+ WARNING
WARNING
WARNING
WARNING
@@ -204,8 +208,10 @@
HINT
WARNING
WARNING
- DO_NOT_SHOW
+ SUGGESTION
DO_NOT_SHOW
+
+ True
DO_NOT_SHOW
WARNING
WARNING
@@ -226,6 +232,7 @@
HINT
DO_NOT_SHOW
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -298,15 +305,21 @@
True
200
CHOP_IF_LONG
+ UseExplicitType
+ UseVarWhenEvident
+ UseVarWhenEvident
False
False
AABB
API
BPM
+ EF
+ FPS
GC
GL
GLSL
HID
+ HSV
HTML
HUD
ID
@@ -717,9 +730,6 @@
</Group>
</TypePattern>
</Patterns>
- 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.
-
<Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
@@ -909,26 +919,82 @@ private void load()
{
$END$
};
+ True
True
True
+ True
True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
- True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
index 4b998cfca3..1d33ab8a54 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState
{
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
index 9dce3c9a0a..2ea52429ab 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
@@ -1,7 +1,7 @@
netstandard2.1
- osu.Game.Rulesets.Sample
+ osu.Game.Rulesets.EmptyScrolling
Library
AnyCPU
osu.Game.Rulesets.EmptyScrolling
diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig
index f3badda9b3..9c7537de4b 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig
+++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig
@@ -10,14 +10,6 @@ trim_trailing_whitespace = true
#Roslyn naming styles
-#PascalCase for public and protected members
-dotnet_naming_style.pascalcase.capitalization = pascal_case
-dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
-dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
-dotnet_naming_rule.public_members_pascalcase.severity = error
-dotnet_naming_rule.public_members_pascalcase.symbols = public_members
-dotnet_naming_rule.public_members_pascalcase.style = pascalcase
-
#camelCase for private members
dotnet_naming_style.camelcase.capitalization = camel_case
@@ -121,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
-csharp_style_var_for_built_in_types = true:none
+csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
@@ -165,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
-csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@@ -176,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = true:warning
-csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_index_operator = false:silent
+csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
@@ -197,4 +189,4 @@ dotnet_diagnostic.IDE0069.severity = none
dotnet_diagnostic.CA2225.severity = none
# Banned APIs
-dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
+dotnet_diagnostic.RS0030.severity = error
diff --git a/Templates/Rulesets/ruleset-scrolling-example/.gitignore b/Templates/Rulesets/ruleset-scrolling-example/.gitignore
index 940794e60f..5b19270ab9 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/.gitignore
+++ b/Templates/Rulesets/ruleset-scrolling-example/.gitignore
@@ -1,7 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
@@ -17,8 +15,6 @@
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
-x64/
-x86/
bld/
[Bb]in/
[Oo]bj/
@@ -42,11 +38,10 @@ TestResult.xml
[Rr]eleasePS/
dlldata.c
-# .NET Core
+# DNX
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
*_i.c
*_p.c
@@ -113,10 +108,6 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
# NCrunch
_NCrunch_*
.*crunch*.local.xml
@@ -166,7 +157,7 @@ PublishScripts/
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
+# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
@@ -196,10 +187,11 @@ ClientBin/
*~
*.dbmdl
*.dbproj.schemaview
-*.jfm
*.pfx
*.publishsettings
+node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@@ -219,7 +211,6 @@ UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
-*.ndf
# Business Intelligence projects
*.rdl.data
@@ -234,10 +225,6 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
# Visual Studio 6 build log
*.plg
@@ -245,9 +232,6 @@ typings/
# Visual Studio 6 workspace options file
*.opt
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -263,26 +247,96 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
+# Cake #
+/tools/**
+/build/tools/**
+/build/temp/**
-# Telerik's JustMock configuration file
-*.jmconfig
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# fastlane
+fastlane/report.xml
+
+# inspectcode
+inspectcodereport.xml
+inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
+
+*.GeneratedMSBuildEditorConfig.editorconfig
+
+# Fody (pulled in by Realm) - schema file
+FodyWeavers.xsd
+**/FodyWeavers.xml
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 0719dd30df..5ecd9cc675 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -20,7 +20,7 @@
WinExe
- net5.0
+ net6.0
osu.Game.Rulesets.Pippidon.Tests
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
index aa8f8739c1..9752e08599 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
@@ -18,9 +18,10 @@
WARNING
HINT
DO_NOT_SHOW
- HINT
- WARNING
- WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
WARNING
WARNING
WARNING
@@ -73,6 +74,7 @@
HINT
WARNING
HINT
+ DO_NOT_SHOW
WARNING
DO_NOT_SHOW
WARNING
@@ -105,8 +107,9 @@
HINT
HINT
WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
WARNING
- DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -120,6 +123,7 @@
WARNING
WARNING
HINT
+ HINT
WARNING
HINT
HINT
@@ -129,7 +133,7 @@
HINT
WARNING
WARNING
- HINT
+ WARNING
WARNING
WARNING
WARNING
@@ -204,8 +208,10 @@
HINT
WARNING
WARNING
- DO_NOT_SHOW
+ SUGGESTION
DO_NOT_SHOW
+
+ True
DO_NOT_SHOW
WARNING
WARNING
@@ -226,6 +232,7 @@
HINT
DO_NOT_SHOW
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -298,15 +305,21 @@
True
200
CHOP_IF_LONG
+ UseExplicitType
+ UseVarWhenEvident
+ UseVarWhenEvident
False
False
AABB
API
BPM
+ EF
+ FPS
GC
GL
GLSL
HID
+ HSV
HTML
HUD
ID
@@ -717,9 +730,6 @@
</Group>
</TypePattern>
</Patterns>
- 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.
-
<Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
@@ -909,26 +919,82 @@ private void load()
{
$END$
};
+ True
True
True
+ True
True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
+ True
+ True
+ True
True
True
True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
- True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
index 7652357b4d..702f6fdb04 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState
{
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
index 61b859f45b..a3607343c9 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -1,7 +1,7 @@
netstandard2.1
- osu.Game.Rulesets.Sample
+ osu.Game.Rulesets.Pippidon
Library
AnyCPU
osu.Game.Rulesets.Pippidon
diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj
index 31a24a301f..4624d3d771 100644
--- a/Templates/osu.Game.Templates.csproj
+++ b/Templates/osu.Game.Templates.csproj
@@ -15,6 +15,7 @@
true
false
content
+ true
diff --git a/fastlane/README.md b/fastlane/README.md
index a400ed9516..9d5e11f7cb 100644
--- a/fastlane/README.md
+++ b/fastlane/README.md
@@ -1,78 +1,109 @@
fastlane documentation
-================
+----
+
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
-```
+```sh
xcode-select --install
```
-Install _fastlane_ using
-```
-[sudo] gem install fastlane -NV
-```
-or alternatively using `brew cask install fastlane`
+For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
# Available Actions
+
## Android
+
### android beta
+
+```sh
+[bundle exec] fastlane android beta
```
-fastlane android beta
-```
+
Deploy to play store
+
### android build_github
+
+```sh
+[bundle exec] fastlane android build_github
```
-fastlane android build_github
-```
+
Deploy to github release
+
### android build
+
+```sh
+[bundle exec] fastlane android build
```
-fastlane android build
-```
+
Compile the project
+
### android update_version
+
+```sh
+[bundle exec] fastlane android update_version
```
-fastlane android update_version
-```
+
----
+
## iOS
+
### ios beta
+
+```sh
+[bundle exec] fastlane ios beta
```
-fastlane ios beta
-```
+
Deploy to testflight
+
### ios build
+
+```sh
+[bundle exec] fastlane ios build
```
-fastlane ios build
-```
+
Compile the project
+
### ios provision
+
+```sh
+[bundle exec] fastlane ios provision
```
-fastlane ios provision
-```
+
Install provisioning profiles using match
+
### ios update_version
+
+```sh
+[bundle exec] fastlane ios update_version
```
-fastlane ios update_version
-```
+
+
### ios testflight_prune_dry
-```
-fastlane ios testflight_prune_dry
+
+```sh
+[bundle exec] fastlane ios testflight_prune_dry
```
+
+
### ios testflight_prune
+
+```sh
+[bundle exec] fastlane ios testflight_prune
```
-fastlane ios testflight_prune
-```
+
----
-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).
+This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
+
+More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
+
+The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/osu.Android.props b/osu.Android.props
index b296c114e9..24a0d20874 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs
index 25bd659a5d..2e83f784d3 100644
--- a/osu.Android/GameplayScreenRotationLocker.cs
+++ b/osu.Android/GameplayScreenRotationLocker.cs
@@ -27,7 +27,7 @@ namespace osu.Android
{
gameActivity.RunOnUiThread(() =>
{
- gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser;
+ gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
});
}
}
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index c9fb539d8a..eebd079f68 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -8,16 +9,18 @@ using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
-using Android.Net;
+using Android.Graphics;
using Android.OS;
using Android.Provider;
using Android.Views;
using osu.Framework.Android;
using osu.Game.Database;
+using Debug = System.Diagnostics.Debug;
+using Uri = Android.Net.Uri;
namespace osu.Android
{
- [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)]
+ [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
@@ -41,6 +44,12 @@ namespace osu.Android
{
private static readonly string[] osu_url_schemes = { "osu", "osump" };
+ ///
+ /// The default screen orientation.
+ ///
+ /// Adjusted on startup to match expected UX for the current device type (phone/tablet).
+ public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
+
private OsuGameAndroid game;
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
@@ -54,8 +63,20 @@ namespace osu.Android
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
handleIntent(Intent);
+ Debug.Assert(Window != null);
+
Window.AddFlags(WindowManagerFlags.Fullscreen);
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
+
+ Debug.Assert(WindowManager?.DefaultDisplay != null);
+ Debug.Assert(Resources?.DisplayMetrics != null);
+
+ Point displaySize = new Point();
+ WindowManager.DefaultDisplay.GetSize(displaySize);
+ float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
+ bool isTablet = smallestWidthDp >= 600f;
+
+ RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape;
}
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
@@ -104,7 +125,7 @@ namespace osu.Android
cursor.MoveToFirst();
- var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
+ int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
string filename = cursor.GetString(filenameColumn);
// SharpCompress requires archive streams to be seekable, which the stream opened by
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 7ec7d53a7e..b944068e78 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -55,7 +55,7 @@ namespace osu.Desktop
}
}
- using (DesktopGameHost host = Host.GetSuitableHost(gameName, true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 89b9ffb94b..b1117bf796 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,6 +1,6 @@
- net5.0
+ net6.0
WinExe
true
A free-to-win rhythm game. Rhythm is just a *click* away!
@@ -26,10 +26,13 @@
-
+
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
index bb22fab51c..bf9467700c 100644
--- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs
+++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks
public class BenchmarkRealmReads : BenchmarkTest
{
private TemporaryNativeStorage storage;
- private RealmContextFactory realmFactory;
+ private RealmAccess realm;
private UpdateThread updateThread;
[Params(1, 100, 1000)]
@@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks
storage = new TemporaryNativeStorage("realm-benchmark");
storage.DeleteDirectory(string.Empty);
- realmFactory = new RealmContextFactory(storage, "client");
+ realm = new RealmAccess(storage, "client");
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
});
@@ -41,9 +41,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkDirectPropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First();
+ var beatmapSet = r.All().First();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks
{
try
{
- var beatmapSet = realmFactory.Context.All().First();
+ var beatmapSet = realm.Realm.All().First();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkRealmLivePropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First().ToLive(realmFactory);
+ var beatmapSet = r.All().First().ToLive(realm);
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks
{
try
{
- var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory);
+ var beatmapSet = realm.Realm.All().First().ToLive(realm);
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkDetachedPropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First().Detach();
+ var beatmapSet = r.All().First().Detach();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks
[GlobalCleanup]
public void Cleanup()
{
- realmFactory?.Dispose();
+ realm?.Dispose();
storage?.Dispose();
updateThread?.Exit();
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 57b914bee6..434c0e0367 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -1,7 +1,7 @@
- net5.0
+ net6.0
Exe
false
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
index 3ba1886d98..33ddac6dfb 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -24,11 +24,16 @@
armv7
UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
+
+ UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 13f2e25f05..fc6d900567 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -9,9 +9,9 @@
WinExe
- net5.0
+ net6.0
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
index 39a58d336d..8e069d7d16 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
@@ -9,6 +9,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
+ ///
+ /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
+ ///
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index f399f48ebd..d576ea3df8 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -3,6 +3,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -15,9 +16,26 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 350;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableFloat SizeMultiplier { get; } = new BindableFloat
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 350;
+
+ protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
private CatchPlayfield playfield;
@@ -31,10 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods
{
private readonly CatchPlayfield playfield;
- public CatchFlashlight(CatchPlayfield playfield)
+ public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield)
+ : base(modFlashlight)
{
this.playfield = playfield;
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
protected override void Update()
@@ -44,19 +63,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index bd742ce6a6..b6af88a771 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index faad95e386..b2a555f89d 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
case CatchSkinComponents.CatchComboCounter:
if (providesComboCounter)
- return new LegacyCatchComboCounter(Skin);
+ return new LegacyCatchComboCounter();
return null;
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
index 33c3867f5a..b4d29988d9 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
private readonly LegacyRollingCounter explosion;
- public LegacyCatchComboCounter(ISkin skin)
+ public LegacyCatchComboCounter()
{
AutoSizeAxes = Axes.Both;
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
index 09ed2dd007..78349334b4 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
@@ -24,11 +24,16 @@
armv7
UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
+
+ UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
new file mode 100644
index 0000000000..7970d5b594
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
@@ -0,0 +1,103 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Tests.Visual;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public class TestSceneManiaModHoldOff : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [Test]
+ public void TestMapHasNoHoldNotes()
+ {
+ var testBeatmap = createModdedBeatmap();
+ Assert.False(testBeatmap.HitObjects.OfType().Any());
+ }
+
+ [Test]
+ public void TestCorrectNoteValues()
+ {
+ var testBeatmap = createRawBeatmap();
+ var noteValues = new List(testBeatmap.HitObjects.OfType().Count());
+
+ foreach (HoldNote h in testBeatmap.HitObjects.OfType())
+ {
+ noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
+ }
+
+ noteValues.Sort();
+ Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 });
+ }
+
+ [Test]
+ public void TestCorrectObjectCount()
+ {
+ // Ensure that the mod produces the expected number of objects when applied.
+
+ var rawBeatmap = createRawBeatmap();
+ var testBeatmap = createModdedBeatmap();
+
+ // Calculate expected number of objects
+ int expectedObjectCount = 0;
+
+ foreach (ManiaHitObject h in rawBeatmap.HitObjects)
+ {
+ // Both notes and hold notes account for at least one object
+ expectedObjectCount++;
+
+ if (h.GetType() == typeof(HoldNote))
+ {
+ double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
+
+ if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
+ {
+ // Should generate an end note if it's longer than the minimum note value
+ expectedObjectCount++;
+ }
+ }
+ }
+
+ Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
+ }
+
+ private static ManiaBeatmap createModdedBeatmap()
+ {
+ var beatmap = createRawBeatmap();
+ var holdOffMod = new ManiaModHoldOff();
+
+ foreach (var hitObject in beatmap.HitObjects)
+ hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty());
+
+ holdOffMod.ApplyToBeatmap(beatmap);
+
+ return beatmap;
+ }
+
+ private static ManiaBeatmap createRawBeatmap()
+ {
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
+
+ // Add test hit objects
+ beatmap.HitObjects.Add(new Note { StartTime = 4000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 4500 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note
+
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
index 215f8fb1d5..8034341d15 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
- Child = new ColumnHitObjectArea(0, new HitObjectContainer())
+ Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
- Child = new ColumnHitObjectArea(1, new HitObjectContainer())
+ Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index d51a6da4f9..ddad2adfea 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -9,9 +9,9 @@
WinExe
- net5.0
+ net6.0
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index 979a04ddf8..5b7a460079 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -9,9 +9,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
+ ///
+ /// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods do not affect the hit window at all in osu-stable.
+ ///
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }
+ ///
+ /// The score multiplier applied via score-reducing mods.
+ ///
[JsonProperty("score_multiplier")]
public double ScoreMultiplier { get; set; }
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 1f82eb7ccd..b17aa7fc4d 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -48,7 +48,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
Mods = mods,
- GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate),
+ // In osu-stable mania, rate-adjustment mods don't affect the hit window.
+ // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
+ GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
};
@@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
- private int getHitWindow300(Mod[] mods)
+ private double getHitWindow300(Mod[] mods)
{
if (isForCurrentRuleset)
{
@@ -121,19 +123,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return applyModAdjustments(47, mods);
- static int applyModAdjustments(double value, Mod[] mods)
+ static double applyModAdjustments(double value, Mod[] mods)
{
if (mods.Any(m => m is ManiaModHardRock))
value /= 1.4;
else if (mods.Any(m => m is ManiaModEasy))
value *= 1.4;
- if (mods.Any(m => m is ManiaModDoubleTime))
- value *= 1.5;
- else if (mods.Any(m => m is ManiaModHalfTime))
- value *= 0.75;
-
- return (int)value;
+ return value;
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
index da9634ba47..17c864a268 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
@@ -16,5 +17,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
[JsonProperty("scaled_score")]
public double ScaledScore { get; set; }
+
+ public override IEnumerable GetAttributesForDisplay()
+ {
+ foreach (var attribute in base.GetAttributesForDisplay())
+ yield return attribute;
+
+ yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty);
+ yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy);
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 8a8c41bb8a..722cb55036 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -43,14 +43,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
- IEnumerable scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
-
- double scoreMultiplier = 1.0;
- foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
- scoreMultiplier *= m.ScoreMultiplier;
-
- // Scale score up, so it's comparable to other keymods
- scaledScore *= 1.0 / scoreMultiplier;
+ if (Attributes.ScoreMultiplier > 0)
+ {
+ // Scale score up, so it's comparable to other keymods
+ scaledScore *= 1.0 / Attributes.ScoreMultiplier;
+ }
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
index 0290230490..c8832dfdfb 100644
--- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
+++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo)
{
- return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
+ return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
index 186fc4b15d..14ca27a11a 100644
--- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs
+++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
+using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania
{
+ [Cached] // Used for touch input, see ColumnTouchInputArea.
public class ManiaInputManager : RulesetInputManager
{
public ManiaInputManager(RulesetInfo ruleset, int variant)
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 6fc7dc018b..180b9ef71b 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania
new ManiaModDifficultyAdjust(),
new ManiaModClassic(),
new ManiaModInvert(),
- new ManiaModConstantSpeed()
+ new ManiaModConstantSpeed(),
+ new ManiaModHoldOff()
};
case ModType.Automation:
@@ -369,10 +370,10 @@ namespace osu.Game.Rulesets.Mania
{
Columns = new[]
{
- new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents)
+ new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
- Height = 250
+ AutoSizeAxes = Axes.Y
}),
}
},
@@ -380,10 +381,21 @@ namespace osu.Game.Rulesets.Mania
{
Columns = new[]
{
- new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }, true),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new UnstableRate(score.HitEvents)
- }))
+ }), true)
}
}
};
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 86a00271e9..8ef5bfd94c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using osuTK;
@@ -16,17 +17,35 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
- private const float default_flashlight_size = 180;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableFloat SizeMultiplier { get; } = new BindableFloat
+ {
+ MinValue = 0.5f,
+ MaxValue = 3f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new ManiaFlashlight();
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = false,
+ Value = false
+ };
+
+ public override float DefaultFlashlightSize => 50;
+
+ protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this);
private class ManiaFlashlight : Flashlight
{
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
- public ManiaFlashlight()
+ public ManiaFlashlight(ManiaModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, default_flashlight_size);
+ FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
AddLayout(flashlightProperties);
}
@@ -46,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods
protected override void OnComboChange(ValueChangedEvent e)
{
+ this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
new file mode 100644
index 0000000000..a65938184c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mods;
+using osu.Framework.Graphics.Sprites;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion
+ {
+ public override string Name => "Hold Off";
+
+ public override string Acronym => "HO";
+
+ public override double ScoreMultiplier => 1;
+
+ public override string Description => @"Replaces all hold notes with normal notes.";
+
+ public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
+
+ public override ModType Type => ModType.Conversion;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
+
+ public const double END_NOTE_ALLOW_THRESHOLD = 0.5;
+
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
+ var newObjects = new List();
+
+ foreach (var h in beatmap.HitObjects.OfType())
+ {
+ // Add a note for the beginning of the hold note
+ newObjects.Add(new Note
+ {
+ Column = h.Column,
+ StartTime = h.StartTime,
+ Samples = h.GetNodeSamples(0)
+ });
+
+ // Don't add an end note if the duration is shorter than the threshold
+ double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc.
+
+ if (noteValue >= END_NOTE_ALLOW_THRESHOLD)
+ {
+ newObjects.Add(new Note
+ {
+ Column = h.Column,
+ StartTime = h.EndTime,
+ Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1)
+ });
+ }
+ }
+
+ maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
+ }
+
+ public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap)
+ {
+ double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength;
+ return holdNote.Duration / beatLength;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
index 1ea45c295c..4cbdaee323 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override ModType Type => ModType.Conversion;
+ public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
+
public void ApplyToBeatmap(IBeatmap beatmap)
{
var maniaBeatmap = (ManiaBeatmap)beatmap;
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index aa0c148caf..aa164f95da 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
index 952fc7ddd6..fdacc75c92 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
@@ -98,8 +98,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
float rightLineWidth = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1;
bool hasLeftLine = leftLineWidth > 0;
- bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
- || isLastColumn;
+ bool hasRightLine = (rightLineWidth > 0 && skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.4m) || isLastColumn;
Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White;
Color4 backgroundColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black;
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9d060944cd..a04f5ef98e 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -62,13 +62,14 @@ namespace osu.Game.Rulesets.Mania.UI
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
- HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
+ HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
background,
- TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
+ TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
+ new ColumnTouchInputArea(this)
};
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
@@ -139,5 +140,50 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
+
+ public class ColumnTouchInputArea : Drawable
+ {
+ private readonly Column column;
+
+ [Resolved(canBeNull: true)]
+ private ManiaInputManager maniaInputManager { get; set; }
+
+ private KeyBindingContainer keyBindingContainer;
+
+ public ColumnTouchInputArea(Column column)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ this.column = column;
+ }
+
+ protected override void LoadComplete()
+ {
+ keyBindingContainer = maniaInputManager?.KeyBindingContainer;
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ keyBindingContainer?.TriggerPressed(column.Action.Value);
+ return base.OnMouseDown(e);
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ keyBindingContainer?.TriggerReleased(column.Action.Value);
+ base.OnMouseUp(e);
+ }
+
+ protected override bool OnTouchDown(TouchDownEvent e)
+ {
+ keyBindingContainer?.TriggerPressed(column.Action.Value);
+ return true;
+ }
+
+ protected override void OnTouchUp(TouchUpEvent e)
+ {
+ keyBindingContainer?.TriggerReleased(column.Action.Value);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index f69d2aafdc..51c138f5e1 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly Drawable hitTarget;
- public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
+ public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
AddRangeInternal(new[]
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
index 267ed1f5f4..15018b464f 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
AlwaysPresent = true
}
}
- }
+ },
}
};
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
index dd032ef1c1..b9f371c049 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
@@ -24,11 +24,16 @@
armv7
UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
+
+ UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
new file mode 100644
index 0000000000..4750c97566
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
+using osu.Game.Screens.Edit.Timing;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ public class TestSceneSliderVelocityAdjust : OsuGameTestScene
+ {
+ private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor;
+
+ private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault();
+
+ private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault();
+
+ private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault();
+
+ private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault();
+
+ private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First();
+
+ private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
+
+ private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true;
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
+ {
+ double? velocity = null;
+
+ AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
+ AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
+
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre));
+ AddStep("start placement", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
+ AddStep("end placement", () => InputManager.Click(MouseButton.Right));
+
+ AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
+
+ AddAssert("slider placed", () => slider != null);
+
+ AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
+
+ AddAssert("ensure one slider placed", () => slider != null);
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+
+ if (adjustVelocity)
+ {
+ AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
+ AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
+
+ AddAssert("velocity adjusted", () =>
+ {
+ Debug.Assert(velocity != null);
+ return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity);
+ });
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+ }
+
+ AddStep("save", () => InputManager.Keys(PlatformAction.Save));
+ AddStep("exit", () => InputManager.Key(Key.Escape));
+
+ AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
+ AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
new file mode 100644
index 0000000000..b8310bc4e7
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Osu.Mods;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModAimAssist : OsuModTestScene
+ {
+ [TestCase(0.1f)]
+ [TestCase(0.5f)]
+ [TestCase(1)]
+ public void TestAimAssist(float strength)
+ {
+ CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAimAssist
+ {
+ AssistStrength = { Value = strength },
+ },
+ PassCondition = () => true,
+ Autoplay = false,
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
new file mode 100644
index 0000000000..de1f61a0bd
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
@@ -0,0 +1,154 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModAlternate : OsuModTestScene
+ {
+ [Test]
+ public void TestInputAtIntro() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAlternate(),
+ PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new HitCircle
+ {
+ StartTime = 1000,
+ Position = new Vector2(100),
+ },
+ },
+ },
+ ReplayFrames = new List
+ {
+ new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
+ new OsuReplayFrame(501, new Vector2(200)),
+ new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
+ }
+ });
+
+ [Test]
+ public void TestInputAlternating() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAlternate(),
+ PassCondition = () => Player.ScoreProcessor.Combo.Value == 4,
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new HitCircle
+ {
+ StartTime = 500,
+ Position = new Vector2(100),
+ },
+ new HitCircle
+ {
+ StartTime = 1000,
+ Position = new Vector2(200, 100),
+ },
+ new HitCircle
+ {
+ StartTime = 1500,
+ Position = new Vector2(300, 100),
+ },
+ new HitCircle
+ {
+ StartTime = 2000,
+ Position = new Vector2(400, 100),
+ },
+ },
+ },
+ ReplayFrames = new List
+ {
+ new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
+ new OsuReplayFrame(501, new Vector2(100)),
+ new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
+ new OsuReplayFrame(1001, new Vector2(200, 100)),
+ new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
+ new OsuReplayFrame(1501, new Vector2(300, 100)),
+ new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
+ new OsuReplayFrame(2001, new Vector2(400, 100)),
+ }
+ });
+
+ [Test]
+ public void TestInputSingular() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAlternate(),
+ PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new HitCircle
+ {
+ StartTime = 500,
+ Position = new Vector2(100),
+ },
+ new HitCircle
+ {
+ StartTime = 1000,
+ Position = new Vector2(200, 100),
+ },
+ },
+ },
+ ReplayFrames = new List
+ {
+ new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
+ new OsuReplayFrame(501, new Vector2(100)),
+ new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
+ }
+ });
+
+ [Test]
+ public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAlternate(),
+ PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ Breaks = new List
+ {
+ new BreakPeriod(500, 2250),
+ },
+ HitObjects = new List
+ {
+ new HitCircle
+ {
+ StartTime = 500,
+ Position = new Vector2(100),
+ },
+ new HitCircle
+ {
+ StartTime = 2500,
+ Position = new Vector2(100),
+ }
+ }
+ },
+ ReplayFrames = new List
+ {
+ new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
+ new OsuReplayFrame(501, new Vector2(100)),
+ new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton),
+ new OsuReplayFrame(2501, new Vector2(100)),
+ }
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 8e226c7ded..44404ca245 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
@@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool isBreak() => Player.IsBreakTime.Value;
- private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha);
+ private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 1f01ba601b..a36f07ff7b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests
public Drawable GetDrawableComponent(ISkinComponent component) => null;
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public ISample GetSample(ISampleInfo sampleInfo) => null;
- public ISkin FindProvider(Func lookupFunction) => null;
public IBindable GetConfig(TLookup lookup)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index fea2e408f6..bd4c3d3345 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -10,9 +10,9 @@
WinExe
- net5.0
+ net6.0
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 126a9b0183..3deed4ea3d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -12,30 +12,68 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
+ ///
+ /// The difficulty corresponding to the aim skill.
+ ///
[JsonProperty("aim_difficulty")]
public double AimDifficulty { get; set; }
+ ///
+ /// The difficulty corresponding to the speed skill.
+ ///
[JsonProperty("speed_difficulty")]
public double SpeedDifficulty { get; set; }
+ ///
+ /// The difficulty corresponding to the flashlight skill.
+ ///
[JsonProperty("flashlight_difficulty")]
public double FlashlightDifficulty { get; set; }
+ ///
+ /// Describes how much of is contributed to by hitcircles or sliders.
+ /// A value closer to 1.0 indicates most of is contributed by hitcircles.
+ /// A value closer to 0.0 indicates most of is contributed by sliders.
+ ///
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
+ ///
+ /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
+ ///
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
+ ///
+ /// The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the overall difficulty value, but have a perceived effect as a result of adjusting audio timing.
+ ///
[JsonProperty("overall_difficulty")]
public double OverallDifficulty { get; set; }
+ ///
+ /// The beatmap's drain rate. This doesn't scale with rate-adjusting mods.
+ ///
public double DrainRate { get; set; }
+ ///
+ /// The number of hitcircles in the beatmap.
+ ///
public int HitCircleCount { get; set; }
+ ///
+ /// The number of sliders in the beatmap.
+ ///
public int SliderCount { get; set; }
+ ///
+ /// The number of spinners in the beatmap.
+ ///
public int SpinnerCount { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
index 6c7760d144..0aeaf7669f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
@@ -22,5 +23,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }
+
+ public override IEnumerable GetAttributesForDisplay()
+ {
+ foreach (var attribute in base.GetAttributesForDisplay())
+ yield return attribute;
+
+ yield return new PerformanceDisplayAttribute(nameof(Aim), "Aim", Aim);
+ yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed);
+ yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy);
+ yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
new file mode 100644
index 0000000000..ed4b139e00
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset
+ {
+ public override string Name => "Aim Assist";
+ public override string Acronym => "AA";
+ public override IconUsage? Icon => FontAwesome.Solid.MousePointer;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "No need to chase the circle – the circle chases you!";
+ public override double ScoreMultiplier => 1;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
+
+ private IFrameStableClock gameplayClock;
+
+ [SettingSource("Assist strength", "How much this mod will assist you.", 0)]
+ public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f)
+ {
+ Precision = 0.05f,
+ MinValue = 0.05f,
+ MaxValue = 1.0f,
+ };
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ gameplayClock = drawableRuleset.FrameStableClock;
+
+ // Hide judgment displays and follow points as they won't make any sense.
+ // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
+ drawableRuleset.Playfield.DisplayJudgements.Value = false;
+ (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
+ }
+
+ public void Update(Playfield playfield)
+ {
+ var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
+
+ foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
+ {
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ easeTo(circle, cursorPos);
+ break;
+
+ case DrawableSlider slider:
+
+ if (!slider.HeadCircle.Result.HasResult)
+ easeTo(slider, cursorPos);
+ else
+ easeTo(slider, cursorPos - slider.Ball.DrawPosition);
+
+ break;
+ }
+ }
+ }
+
+ private void easeTo(DrawableHitObject hitObject, Vector2 destination)
+ {
+ double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value);
+
+ float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
+ float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
+
+ hitObject.Position = new Vector2(x, y);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
new file mode 100644
index 0000000000..46b97dd23b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
@@ -0,0 +1,106 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer
+ {
+ public override string Name => @"Alternate";
+ public override string Acronym => @"AL";
+ public override string Description => @"Don't use the same key twice in a row!";
+ public override double ScoreMultiplier => 1.0;
+ public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
+ public override ModType Type => ModType.Conversion;
+ public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
+
+ private double firstObjectValidJudgementTime;
+ private IBindable isBreakTime;
+ private const double flash_duration = 1000;
+ private OsuAction? lastActionPressed;
+ private DrawableRuleset ruleset;
+
+ private IFrameStableClock gameplayClock;
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ ruleset = drawableRuleset;
+ drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
+
+ var firstHitObject = ruleset.Objects.FirstOrDefault();
+ firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0);
+
+ gameplayClock = drawableRuleset.FrameStableClock;
+ }
+
+ public void ApplyToPlayer(Player player)
+ {
+ isBreakTime = player.IsBreakTime.GetBoundCopy();
+ isBreakTime.ValueChanged += e =>
+ {
+ if (e.NewValue)
+ lastActionPressed = null;
+ };
+ }
+
+ private bool checkCorrectAction(OsuAction action)
+ {
+ if (isBreakTime.Value)
+ return true;
+
+ if (gameplayClock.CurrentTime < firstObjectValidJudgementTime)
+ return true;
+
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ case OsuAction.RightButton:
+ break;
+
+ // Any action which is not left or right button should be ignored.
+ default:
+ return true;
+ }
+
+ if (lastActionPressed != action)
+ {
+ // User alternated correctly.
+ lastActionPressed = action;
+ return true;
+ }
+
+ ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
+ return false;
+ }
+
+ private class InputInterceptor : Component, IKeyBindingHandler
+ {
+ private readonly OsuModAlternate mod;
+
+ public InputInterceptor(OsuModAlternate mod)
+ {
+ this.mod = mod;
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ // if the pressed action is incorrect, block it from reaching gameplay.
+ => !mod.checkCorrectAction(e.Action);
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index aac830801b..983964d639 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) };
public bool PerformFail() => false;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
index 106edfb623..2668013321 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAutoplay : ModAutoplay
{
- public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
index f478790134..ff31cfcd18 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModCinema : ModCinema
{
- public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 300a9d48aa..38c84be295 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
@@ -21,27 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 180;
-
private const double default_follow_delay = 120;
- private OsuFlashlight flashlight;
-
- public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
-
- public void ApplyToDrawableHitObject(DrawableHitObject drawable)
- {
- if (drawable is DrawableSlider s)
- s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
- }
-
- public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
- {
- base.ApplyToDrawableRuleset(drawableRuleset);
-
- flashlight.FollowDelay = FollowDelay.Value;
- }
-
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay)
{
@@ -50,13 +30,45 @@ namespace osu.Game.Rulesets.Osu.Mods
Precision = default_follow_delay,
};
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableFloat SizeMultiplier { get; } = new BindableFloat
+ {
+ MinValue = 0.5f,
+ MaxValue = 2f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
+
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 180;
+
+ private OsuFlashlight flashlight;
+
+ protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
+
+ public void ApplyToDrawableHitObject(DrawableHitObject drawable)
+ {
+ if (drawable is DrawableSlider s)
+ s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
+ }
+
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
{
- public double FollowDelay { private get; set; }
+ private readonly double followDelay;
- public OsuFlashlight()
+ public OsuFlashlight(OsuModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ followDelay = modFlashlight.FollowDelay.Value;
+
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
public void OnSliderTrackingChange(ValueChangedEvent e)
@@ -71,24 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods
var destination = e.MousePosition;
FlashlightPosition = Interpolation.ValueAt(
- Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out);
+ Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out);
return base.OnMouseMove(e);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 5d191119b9..1bf63ef6d4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -87,8 +87,8 @@ namespace osu.Game.Rulesets.Osu.Mods
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
break;
- case DrawableSpinner _:
- requiresHold = true;
+ case DrawableSpinner spinner:
+ requiresHold |= spinner.HitObject.SpinsRequired > 0;
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 8122ab563e..28c3b069b6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Fun;
public override string Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) };
private float theta;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index ff6ba6e121..40a05400ea 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Fun;
public override string Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) };
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
private const int wiggle_strength = 10; // Higher = stronger wiggles
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 18e4bb259c..ad00a025a1 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -169,6 +169,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModClassic(),
new OsuModRandom(),
new OsuModMirror(),
+ new OsuModAlternate(),
};
case ModType.Automation:
@@ -193,6 +194,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModApproachDifferent(),
new OsuModMuted(),
new OsuModNoScope(),
+ new OsuModAimAssist(),
};
case ModType.System:
@@ -277,22 +279,10 @@ namespace osu.Game.Rulesets.Osu
{
Columns = new[]
{
- new StatisticItem("Timing Distribution",
- new HitEventTimingDistributionGraph(timedHitEvents)
- {
- RelativeSizeAxes = Axes.X,
- Height = 250
- }),
- }
- },
- new StatisticRow
- {
- Columns = new[]
- {
- new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
+ new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
- Height = 250
+ AutoSizeAxes = Axes.Y
}),
}
},
@@ -300,10 +290,32 @@ namespace osu.Game.Rulesets.Osu
{
Columns = new[]
{
- new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }, true),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }, true),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new UnstableRate(timedHitEvents)
- }))
+ }), true)
}
}
};
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
index 7d696dfb79..ea36ecc399 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 2233a547b9..bc1e80cd12 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly ProxyContainer approachCircles;
private readonly ProxyContainer spinnerProxies;
private readonly JudgementContainer judgementLayer;
- private readonly FollowPointRenderer followPoints;
+
+ public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
- followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
+ FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
@@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI
protected override void OnHitObjectAdded(HitObject hitObject)
{
base.OnHitObjectAdded(hitObject);
- followPoints.AddFollowPoints((OsuHitObject)hitObject);
+ FollowPoints.AddFollowPoints((OsuHitObject)hitObject);
}
protected override void OnHitObjectRemoved(HitObject hitObject)
{
base.OnHitObjectRemoved(hitObject);
- followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
+ FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject);
}
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
index ac658cd14e..65c47d2115 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
@@ -24,11 +24,16 @@
armv7
UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
+
+ UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
deleted file mode 100644
index 42ab84714a..0000000000
--- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using NUnit.Framework;
-using osu.Framework.Input;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Taiko.Beatmaps;
-using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Setup;
-using osu.Game.Screens.Menu;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Visual;
-using osuTK.Input;
-
-namespace osu.Game.Rulesets.Taiko.Tests.Editor
-{
- public class TestSceneEditorSaving : OsuGameTestScene
- {
- private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault();
-
- private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
-
- ///
- /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
- /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable.
- ///
- [Test]
- public void TestNewBeatmapSaveThenLoad()
- {
- AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
- AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
-
- PushAndConfirm(() => new EditorLoader());
-
- AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
-
- AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
-
- AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
- AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2);
- AddStep("Set artist and title", () =>
- {
- editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
- editorBeatmap.BeatmapInfo.Metadata.Title = "title";
- });
- AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
-
- checkMutations();
-
- AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
-
- checkMutations();
-
- AddStep("Exit", () => InputManager.Key(Key.Escape));
-
- AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
-
- PushAndConfirm(() => new PlaySongSelect());
-
- AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
- AddStep("Open options", () => InputManager.Key(Key.F3));
- AddStep("Enter editor", () => InputManager.Key(Key.Number5));
-
- AddUntilStep("Wait for editor load", () => editor != null);
-
- checkMutations();
- }
-
- private void checkMutations()
- {
- AddAssert("Beatmap has correct slider multiplier", () =>
- {
- // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
- // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
- var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
- taikoDifficulty.CopyFrom(editorBeatmap.Difficulty);
- return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
- });
- AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
- AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
new file mode 100644
index 0000000000..33c2ba532e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Editor
+{
+ public class TestSceneTaikoEditorSaving : EditorSavingTestScene
+ {
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
+
+ [Test]
+ public void TestTaikoSliderMultiplier()
+ {
+ AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2);
+
+ SaveEditor();
+
+ AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ bool assertTaikoSliderMulitplier()
+ {
+ // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
+ // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
+ var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
+ taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty);
+ return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index 0be005e1c4..eec88d7bf8 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Player.ScoreProcessor.NewJudgement += b => judged = true;
});
AddUntilStep("swell judged", () => judged);
- AddAssert("failed", () => Player.HasFailed);
+ AddAssert("failed", () => Player.GameplayState.HasFailed);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index ad3713e047..a6b8eb8651 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -9,9 +9,9 @@
WinExe
- net5.0
+ net6.0
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 613874b7d6..b1d8575de4 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap converted = base.ConvertBeatmap(original, cancellationToken);
- if (original.BeatmapInfo.RulesetID == 3)
+ if (original.BeatmapInfo.Ruleset.OnlineID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index 31f5a6f570..3dc5438072 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -9,18 +9,39 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
+ ///
+ /// The difficulty corresponding to the stamina skill.
+ ///
[JsonProperty("stamina_difficulty")]
public double StaminaDifficulty { get; set; }
+ ///
+ /// The difficulty corresponding to the rhythm skill.
+ ///
[JsonProperty("rhythm_difficulty")]
public double RhythmDifficulty { get; set; }
+ ///
+ /// The difficulty corresponding to the colour skill.
+ ///
[JsonProperty("colour_difficulty")]
public double ColourDifficulty { get; set; }
+ ///
+ /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
+ ///
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
+ ///
+ /// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing.
+ ///
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index 80552880ea..fa5c0202dd 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
@@ -13,5 +14,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
+
+ public override IEnumerable GetAttributesForDisplay()
+ {
+ foreach (var attribute in base.GetAttributesForDisplay())
+ yield return attribute;
+
+ yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty);
+ yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy);
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 0a325f174e..beec785fe8 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@@ -16,9 +17,26 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 250;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableFloat SizeMultiplier { get; } = new BindableFloat
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 250;
+
+ protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
private TaikoPlayfield playfield;
@@ -33,7 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
private readonly TaikoPlayfield taikoPlayfield;
- public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
+ public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
+ : base(modFlashlight)
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = getSizeFor(0);
@@ -43,15 +62,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private Vector2 getSizeFor(int combo)
{
- float size = default_flashlight_size;
-
- if (combo > 200)
- size *= 0.8f;
- else if (combo > 100)
- size *= 0.9f;
-
// Preserve flashlight size through the playfield's aspect adjustment.
- return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
+ return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void OnComboChange(ValueChangedEvent e)
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
index 138e8f9785..2f9b6c7f60 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
index 8ca996159b..a106c4f629 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
if (!effectPoint.KiaiMode)
return;
- if (beatIndex % (int)timingPoint.TimeSignature != 0)
+ if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
return;
double duration = timingPoint.BeatLength * 2;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index ca860f24c3..e56aabaf9d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -213,10 +213,10 @@ namespace osu.Game.Rulesets.Taiko
{
Columns = new[]
{
- new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
+ new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
- Height = 250
+ AutoSizeAxes = Axes.Y
}),
}
},
@@ -224,10 +224,21 @@ namespace osu.Game.Rulesets.Taiko
{
Columns = new[]
{
- new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }, true),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new UnstableRate(timedHitEvents)
- }))
+ }), true)
}
}
};
diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist
index 1a89345bc5..ed0c2e4dbf 100644
--- a/osu.Game.Tests.iOS/Info.plist
+++ b/osu.Game.Tests.iOS/Info.plist
@@ -24,11 +24,16 @@
armv7
UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
+
+ UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationLandscapeLeft
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 6ec14e6351..468cb7683c 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
@@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(119637);
Assert.AreEqual(119637, timingPoint.Time);
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time);
@@ -794,5 +794,74 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(path.Distance, Is.EqualTo(1));
}
}
+
+ [Test]
+ public void TestLegacyDefaultsPreserved()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var memoryStream = new MemoryStream())
+ using (var stream = new LineBufferedReader(memoryStream))
+ {
+ var decoded = decoder.Decode(stream);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
+ Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
+ Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
+ Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
+ Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
+ Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
+ Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal));
+ Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
+ Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
+ });
+ }
+ }
+
+ [Test]
+ public void TestUndefinedApproachRateInheritsOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedBeforeOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedAfterOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index d12da1a22f..d19b3c71f1 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
private IBeatmap convert(IBeatmap beatmap)
{
- switch (beatmap.BeatmapInfo.RulesetID)
+ switch (beatmap.BeatmapInfo.Ruleset.OnlineID)
{
case 0:
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 06ed638e0a..2eb75259d9 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
index 7aa2dc7093..9e440c6bce 100644
--- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
+++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
@@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
{
- var realmContextFactory = osu.Dependencies.Get();
+ var realm = osu.Dependencies.Get();
- realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout));
+ realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout));
// TODO: add back some extra checks outside of the realm ones?
// var set = queryBeatmapSets().First();
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 53e4ef07e7..5cbede54f5 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -155,7 +155,7 @@ namespace osu.Game.Tests.Collections.IO
}
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null))
{
try
{
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index 227314cffd..2c7d0211a0 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -38,12 +38,12 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDetachBeatmapSet()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
- ILive? beatmapSet;
+ Live? beatmapSet;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
beatmapSet = await importer.Import(reader);
@@ -82,12 +82,12 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateDetachedBeatmapSet()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
- ILive? beatmapSet;
+ Live? beatmapSet;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
beatmapSet = await importer.Import(reader);
@@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportBeatmapThenCleanup()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
- ILive? imported;
+ Live? imported;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
imported = await importer.Import(reader);
- Assert.AreEqual(1, realmFactory.Context.All().Count());
+ Assert.AreEqual(1, realm.Realm.All().Count());
Assert.NotNull(imported);
Debug.Assert(imported != null);
imported.PerformWrite(s => s.DeletePending = true);
- Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending));
+ Assert.AreEqual(1, realm.Realm.All().Count(s => s.DeletePending));
}
});
Logger.Log("Running with no work to purge pending deletions");
- RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); });
+ RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); });
}
[Test]
public void TestImportWhenClosed()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- await LoadOszIntoStore(importer, realmFactory.Context);
+ await LoadOszIntoStore(importer, realm.Realm);
});
}
[Test]
public void TestAccessFileAfterImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var beatmap = imported.Beatmaps.First();
var file = beatmap.File;
@@ -198,33 +198,33 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDelete()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenDeleteFromStream()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? tempPath = TestResources.GetTestBeatmapForImport();
- ILive? importedSet;
+ Live? importedSet;
using (var stream = File.OpenRead(tempPath))
{
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
Assert.NotNull(importedSet);
@@ -233,39 +233,39 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
File.Delete(tempPath);
- var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
+ var imported = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestImportThenImportWithReZip()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -274,7 +274,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
string hashBefore = hashFile(temp);
@@ -292,7 +292,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithChangedHashedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -323,9 +323,9 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First());
+ await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -343,7 +343,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
@@ -363,10 +363,10 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportThenImportWithChangedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -392,7 +392,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithDifferentFilename()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -423,7 +423,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -440,7 +440,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -460,12 +460,12 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportCorruptThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var firstFile = imported.Files.First();
@@ -476,7 +476,7 @@ namespace osu.Game.Tests.Database
using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
stream.WriteByte(0);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
@@ -485,18 +485,18 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestModelCreationFailureDoesntReturn()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var progressNotification = new ImportProgressNotification();
@@ -510,8 +510,8 @@ namespace osu.Game.Tests.Database
new ImportTask(zipStream, string.Empty)
);
- checkBeatmapSetCount(realmFactory.Context, 0);
- checkBeatmapCount(realmFactory.Context, 0);
+ checkBeatmapSetCount(realm.Realm, 0);
+ checkBeatmapCount(realm.Realm, 0);
Assert.IsEmpty(imported);
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
@@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestRollbackOnFailure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
int loggedExceptionCount = 0;
@@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database
Interlocked.Increment(ref loggedExceptionCount);
};
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() => imported.Hash += "-changed");
+ realm.Realm.Write(() => imported.Hash += "-changed");
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
+ checkSingleReferencedFileCount(realm.Realm, 18);
string? brokenTempFilename = TestResources.GetTestBeatmapForImport();
@@ -565,10 +565,10 @@ namespace osu.Game.Tests.Database
{
}
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkSingleReferencedFileCount(realm.Realm, 18);
Assert.AreEqual(1, loggedExceptionCount);
@@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportOptimisedPath()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -601,20 +601,52 @@ namespace osu.Game.Tests.Database
}
[Test]
- public void TestImportThenDeleteThenImportNonOptimisedPath()
+ public void TestImportThenReimportAfterMissingFiles()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
using var store = new RulesetStore(realmFactory, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realmFactory.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ // intentionally nuke all files
+ storage.DeleteDirectory("files");
+
+ Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm);
+
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID == importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+ Assert.IsFalse(imported.DeletePending);
+ Assert.IsFalse(importedSecondTime.DeletePending);
+
+ // check that the files now exist, even though they were deleted above.
+ Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+ });
+ }
+
+ [Test]
+ public void TestImportThenDeleteThenImportNonOptimisedPath()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using var importer = new NonOptimisedBeatmapImporter(realm, storage);
+ using var store = new RulesetStore(realm, storage);
+
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+
+ deleteBeatmapSet(imported, realm.Realm);
+
+ Assert.IsTrue(imported.DeletePending);
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -627,22 +659,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() =>
+ realm.Realm.Write(() =>
{
foreach (var b in imported.Beatmaps)
b.OnlineID = -1;
});
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@@ -653,10 +685,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateBeatmapIDs()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var metadata = new BeatmapMetadata
{
@@ -667,7 +699,7 @@ namespace osu.Game.Tests.Database
}
};
- var ruleset = realmFactory.Context.All().First();
+ var ruleset = realm.Realm.All().First();
var toImport = new BeatmapSetInfo
{
@@ -686,7 +718,7 @@ namespace osu.Game.Tests.Database
}
};
- var imported = await importer.Import(toImport);
+ var imported = importer.Import(toImport);
Assert.NotNull(imported);
Debug.Assert(imported != null);
@@ -699,15 +731,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWhenFileOpen()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
await importer.Import(temp);
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
});
@@ -716,10 +748,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateHashes()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -740,7 +772,7 @@ namespace osu.Game.Tests.Database
await importer.Import(temp);
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
finally
{
@@ -752,10 +784,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportNestedStructure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -780,7 +812,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
}
@@ -794,10 +826,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithIgnoredDirectoryInArchive()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -830,7 +862,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
@@ -845,22 +877,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateBeatmapInfo()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
await importer.Import(temp);
// Update via the beatmap, not the beatmap info, to ensure correct linking
- BeatmapSetInfo setToUpdate = realmFactory.Context.All().First();
+ BeatmapSetInfo setToUpdate = realm.Realm.All().First();
var beatmapToUpdate = setToUpdate.Beatmaps.First();
- realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
+ realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated");
- BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID);
+ BeatmapInfo updatedInfo = realm.Realm.All().First(b => b.ID == beatmapToUpdate.ID);
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
});
}
@@ -1004,8 +1036,8 @@ namespace osu.Game.Tests.Database
public class NonOptimisedBeatmapImporter : BeatmapImporter
{
- public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
- : base(realmFactory, storage)
+ public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage)
+ : base(realm, storage)
{
}
diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs
index 3cb4705381..98b0ed99b5 100644
--- a/osu.Game.Tests/Database/FileStoreTests.cs
+++ b/osu.Game.Tests/Database/FileStoreTests.cs
@@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportFile()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportSameFileTwice()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDontPurgeReferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
@@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestPurgeUnreferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index 9ebe94b383..8262ef18d4 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestConstructRealm()
{
- RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); });
+ RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); });
}
[Test]
public void TestBlockOperations()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
@@ -42,24 +42,25 @@ namespace osu.Game.Tests.Database
[Test]
public void TestNestedContextCreationWithSubscription()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
bool callbackRan = false;
- realmFactory.Run(realm =>
+ realm.RegisterCustomSubscription(r =>
{
- var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) =>
{
- realmFactory.Run(_ =>
+ realm.Run(_ =>
{
callbackRan = true;
});
});
// Force the callback above to run.
- realmFactory.Run(r => r.Refresh());
+ realm.Run(rr => rr.Refresh());
subscription?.Dispose();
+ return null;
});
Assert.IsTrue(callbackRan);
@@ -69,14 +70,14 @@ namespace osu.Game.Tests.Database
[Test]
public void TestBlockOperationsWithContention()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
Task.Factory.StartNew(() =>
{
- realmFactory.Run(_ =>
+ realm.Run(_ =>
{
hasThreadedUsage.Set();
@@ -88,12 +89,17 @@ namespace osu.Game.Tests.Database
Assert.Throws(() =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
stopThreadedUsage.Set();
+
+ // Ensure we can block a second time after the usage has ended.
+ using (realm.BlockAllOperations())
+ {
+ }
});
}
}
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 7b1cf763d6..4bc1f5078a 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveEquality()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory));
+ Live beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
- ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory));
+ Live beatmap2 = realm.Run(r => r.All().First().ToLive(realm));
Assert.AreEqual(beatmap, beatmap2);
});
@@ -34,29 +34,27 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterStorageMigrate()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- realm.Write(r => r.Add(beatmap));
+ r.Write(_ => r.Add(beatmap));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
- using (realmFactory.BlockAllOperations())
- {
- // recycle realm before migrating
- }
-
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
- storage.Migrate(migratedStorage);
+ using (realm.BlockAllOperations())
+ {
+ storage.Migrate(migratedStorage);
+ }
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
}
@@ -66,13 +64,13 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterAttach()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
- realmFactory.Run(realm => realm.Write(r => r.Add(beatmap)));
+ realm.Run(r => r.Write(_ => r.Add(beatmap)));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
@@ -98,16 +96,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedReadWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -127,16 +125,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedWriteWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -153,10 +151,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessNonManaged()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
Assert.DoesNotThrow(() =>
{
@@ -168,17 +166,17 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -193,7 +191,7 @@ namespace osu.Game.Tests.Database
});
// Can't be used, even from within a valid context.
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
Assert.Throws(() =>
{
@@ -207,16 +205,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithoutOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -235,18 +233,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveAssumptions()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
int changesTriggered = 0;
- realmFactory.Run(outerRealm =>
+ realm.RegisterCustomSubscription(outerRealm =>
{
outerRealm.All().QueryAsyncWithNotifications(gotChange);
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(innerRealm =>
+ realm.Run(innerRealm =>
{
var ruleset = CreateRuleset();
var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
@@ -255,7 +253,7 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live.
innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -282,6 +280,8 @@ namespace osu.Game.Tests.Database
r.Remove(resolved);
});
});
+
+ return null;
});
void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
new file mode 100644
index 0000000000..d62ce3b585
--- /dev/null
+++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Tests.Resources;
+using Realms;
+
+#nullable enable
+
+namespace osu.Game.Tests.Database
+{
+ [TestFixture]
+ public class RealmSubscriptionRegistrationTests : RealmTest
+ {
+ [Test]
+ public void TestSubscriptionWithContextLoss()
+ {
+ IEnumerable? resolvedItems = null;
+ ChangeSet? lastChanges = null;
+
+ RunTestWithRealm((realm, _) =>
+ {
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var registration = realm.RegisterForNotifications(r => r.All(), onChanged);
+
+ testEventsArriving(true);
+
+ // All normal until here.
+ // Now let's yank the main realm context.
+ resolvedItems = null;
+ lastChanges = null;
+
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Empty);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(true);
+
+ // Now let's try unsubscribing.
+ resolvedItems = null;
+ lastChanges = null;
+
+ registration.Dispose();
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ // And make sure even after another context loss we don't get firings.
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ void testEventsArriving(bool shouldArrive)
+ {
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(resolvedItems, Has.One.Items);
+ else
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r =>
+ {
+ r.RemoveAll();
+ r.RemoveAll();
+ });
+
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
+ else
+ Assert.That(lastChanges, Is.Null);
+ }
+ });
+
+ void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error)
+ {
+ if (changes == null)
+ resolvedItems = sender;
+
+ lastChanges = changes;
+ }
+ }
+
+ [Test]
+ public void TestCustomRegisterWithContextLoss()
+ {
+ RunTestWithRealm((realm, _) =>
+ {
+ BeatmapSetInfo? beatmapSetInfo = null;
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var subscription = realm.RegisterCustomSubscription(r =>
+ {
+ beatmapSetInfo = r.All().First();
+
+ return new InvokeOnDisposal(() => beatmapSetInfo = null);
+ });
+
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ using (realm.BlockAllOperations())
+ {
+ // custom disposal action fired when context lost.
+ Assert.That(beatmapSetInfo, Is.Null);
+ }
+
+ // re-registration after context restore.
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ subscription.Dispose();
+
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ using (realm.BlockAllOperations())
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Null);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 0cee165f75..838759c991 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty);
}
- protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -39,22 +39,22 @@ namespace osu.Game.Tests.Database
// ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
- Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}");
}
}));
}
}
- protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -62,15 +62,15 @@ namespace osu.Game.Tests.Database
{
var testStorage = storage.GetStorageForDirectory(caller);
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- await testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ await testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
}
}));
}
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database
}
protected static RulesetInfo CreateRuleset() =>
- new RulesetInfo(0, "osu!", "osu", true);
+ new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true };
private class RealmTestGame : Framework.Game
{
@@ -138,11 +138,11 @@ namespace osu.Game.Tests.Database
}
}
- private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
+ private static long getFileSize(Storage testStorage, RealmAccess realm)
{
try
{
- using (var stream = testStorage.GetStream(realmFactory.Filename))
+ using (var stream = testStorage.GetStream(realm.Filename))
return stream?.Length ?? 0;
}
catch
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index 4416da6f92..7544142b70 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database
[Test]
public void TestCreateStore()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realm.Realm.All().Count());
});
}
[Test]
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RulesetStore(realmFactory, storage);
- var rulesets2 = new RulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
+ var rulesets2 = new RulesetStore(realm, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realm.Realm.All().Count());
});
}
[Test]
public void TestRetrievedRulesetsAreDetached()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
index c1041e9fd6..891801865f 100644
--- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
+++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database
private RealmKeyBindingStore keyBindingStore;
- private RealmContextFactory realmContextFactory;
+ private RealmAccess realm;
[SetUp]
public void SetUp()
@@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database
storage = new NativeStorage(directory.FullName);
- realmContextFactory = new RealmContextFactory(storage, "test");
- keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider());
+ realm = new RealmAccess(storage, "test");
+ keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
}
[Test]
@@ -60,11 +60,11 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer();
// Add some excess bindings for an action which only supports 1.
- realmContextFactory.Write(realm =>
+ realm.Write(r =>
{
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
});
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
@@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database
private int queryCount(GlobalAction? match = null)
{
- return realmContextFactory.Run(realm =>
+ return realm.Run(r =>
{
- var results = realm.All();
+ var results = r.All();
if (match.HasValue)
results = results.Where(k => k.ActionInt == (int)match.Value);
return results.Count();
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer, Enumerable.Empty());
- realmContextFactory.Run(outerRealm =>
+ realm.Run(outerRealm =>
{
var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database
var tsr = ThreadSafeReference.Create(backBinding);
- realmContextFactory.Run(innerRealm =>
+ realm.Run(innerRealm =>
{
var binding = innerRealm.ResolveReference(tsr);
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database
[TearDown]
public void TearDown()
{
- realmContextFactory.Dispose();
+ realm.Dispose();
storage.DeleteDirectory(string.Empty);
}
diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
index 242fec2f68..53c85defae 100644
--- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
// Should fail to load, but not produce an error due to the extension not being expected to load.
- Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
+ Assert.IsEmpty(check.Run(getContext(null)));
}
[Test]
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks
{
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
{
- Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
+ Assert.IsEmpty(check.Run(getContext(resourceStream)));
}
}
@@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks
}
}
- private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
+ private BeatmapVerifierContext getContext(Stream resourceStream)
{
var mockWorkingBeatmap = new Mock(beatmap, null, null);
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny())).Returns(resourceStream);
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 34f70659e3..5553c67141 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public ISkin FindProvider(Func lookupFunction) => null;
-
public IBindable GetConfig(TLookup lookup)
{
switch (lookup)
diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
index 935bc07733..77b402ad3c 100644
--- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
@@ -2,7 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Bindables;
using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
@@ -12,47 +18,93 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneMasterGameplayClockContainer : OsuTestScene
{
+ private OsuConfigManager localConfig;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
+ }
+
[Test]
public void TestStartThenElapsedTime()
{
- GameplayClockContainer gcc = null;
+ GameplayClockContainer gameplayClockContainer = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
- Add(gcc = new MasterGameplayClockContainer(working, 0));
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
});
- AddStep("start clock", () => gcc.Start());
- AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
+ AddStep("start clock", () => gameplayClockContainer.Start());
+ AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0);
}
[Test]
public void TestElapseThenReset()
{
- GameplayClockContainer gcc = null;
+ GameplayClockContainer gameplayClockContainer = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
- Add(gcc = new MasterGameplayClockContainer(working, 0));
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
});
- AddStep("start clock", () => gcc.Start());
- AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000);
+ AddStep("start clock", () => gameplayClockContainer.Start());
+ AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000);
double timeAtReset = 0;
AddStep("reset clock", () =>
{
- timeAtReset = gcc.GameplayClock.CurrentTime;
- gcc.Reset();
+ timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime;
+ gameplayClockContainer.Reset();
});
- AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset);
+ AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset);
+ }
+
+ [Test]
+ public void TestSeekPerformsInGameplayTime(
+ [Values(1.0, 0.5, 2.0)] double clockRate,
+ [Values(0.0, 200.0, -200.0)] double userOffset,
+ [Values(false, true)] bool whileStopped)
+ {
+ ClockBackedTestWorkingBeatmap working = null;
+ GameplayClockContainer gameplayClockContainer = null;
+
+ AddStep("create container", () =>
+ {
+ working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio);
+ working.LoadTrack();
+
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
+
+ if (whileStopped)
+ gameplayClockContainer.Stop();
+
+ gameplayClockContainer.Reset();
+ });
+
+ AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate)));
+ AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
+
+ AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
+ AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f));
+
+ AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
+ AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f));
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ localConfig?.Dispose();
+ base.Dispose(isDisposing);
}
}
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
index 432e3df95e..70ba868de6 100644
--- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
@@ -1,11 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
@@ -42,6 +48,43 @@ namespace osu.Game.Tests.Gameplay
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
}
+ [Test]
+ public void TestResetFromReplayFrame()
+ {
+ var beatmap = new Beatmap { HitObjects = { new HitCircle() } };
+
+ var scoreProcessor = new ScoreProcessor();
+ scoreProcessor.ApplyBeatmap(beatmap);
+
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
+ Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
+ Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+
+ // No header shouldn't cause any change
+ scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
+
+ Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
+ Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+
+ // Reset with a miss instead.
+ scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
+ {
+ Header = new FrameHeader(0, 0, 0, new Dictionary { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
+ });
+
+ Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
+ Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+
+ // Reset with no judged hit.
+ scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
+ {
+ Header = new FrameHeader(0, 0, 0, new Dictionary(), DateTimeOffset.Now)
+ });
+
+ Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
+ Assert.That(scoreProcessor.JudgedHits, Is.Zero);
+ }
+
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index f0ebd7a8cc..88862ea28b 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay
public AudioManager AudioManager => Audio;
public IResourceStore Files => null;
public new IResourceStore Resources => base.Resources;
- public RealmContextFactory RealmContextFactory => null;
+ public RealmAccess RealmAccess => null;
public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null;
#endregion
diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
index 834c05fd08..6ae8231deb 100644
--- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
+++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual
const int beat_length_numerator = 2000;
const int beat_length_denominator = 7;
- const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
+ TimeSignature signature = TimeSignature.SimpleQuadruple;
var beatmap = new Beatmap
{
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
{
var barLine = barLines[i * beat_length_denominator];
- int expectedTime = beat_length_numerator * (int)signature * i;
+ int expectedTime = beat_length_numerator * signature.Numerator * i;
// every seventh bar's start time should be at least greater than the whole number we expect.
// It cannot be less, as that can affect overlapping scroll algorithms
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
// check major/minor lines for good measure too
- Assert.AreEqual(i % (int)signature == 0, barLine.Major);
+ Assert.AreEqual(i % signature.Numerator == 0, barLine.Major);
}
}
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 61ef31e07e..834930a05e 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual
Assert.That(osuStorage, Is.Not.Null);
+ // In the following tests, realm files are ignored as
+ // - in the case of checking the source, interacting with the pipe files (client.realm.note) may
+ // lead to unexpected behaviour.
+ // - in the case of checking the destination, the files may have already been recreated by the game
+ // as part of the standard migration flow.
+
foreach (string file in osuStorage.IgnoreFiles)
{
- // avoid touching realm files which may be a pipe and break everything.
- // this is also done locally inside OsuStorage via the IgnoreFiles list.
- if (file.EndsWith(".ini", StringComparison.Ordinal))
+ if (!file.Contains("realm", StringComparison.Ordinal))
+ {
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
- Assert.That(storage.Exists(file), Is.False);
+ Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
+ }
}
foreach (string dir in osuStorage.IgnoreDirectories)
{
- Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
- Assert.That(storage.ExistsDirectory(dir), Is.False);
+ if (!dir.Contains("realm", StringComparison.Ordinal))
+ {
+ Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
+ Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
+ }
}
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}"));
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 74904f4585..33204d33a7 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
- Ruleset = new RulesetInfo { OnlineID = 0 },
+ Ruleset = new RulesetInfo
+ {
+ ShortName = "osu",
+ OnlineID = 0
+ },
StarRating = 4.0d,
Difficulty = new BeatmapDifficulty
{
@@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
- Ruleset = new RulesetInfo { OnlineID = 6 }
+ Ruleset = new RulesetInfo { ShortName = "catch" }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
@@ -78,6 +82,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsFalse(carouselItem.Filtered.Value);
}
+ [Test]
+ public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { OnlineID = -1 },
+ AllowConvertedBeatmaps = true
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.IsFalse(carouselItem.Filtered.Value);
+ }
+
[Test]
[TestCase(true)]
[TestCase(false)]
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index 0c49a18c8f..4adb7002a0 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -21,8 +21,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
{
var user = new APIUser { Id = 33 };
- AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+ AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3);
+ AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2);
}
[Test]
@@ -30,11 +30,11 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
{
var user = new APIUser { Id = 44 };
- AddStep("add user", () => Client.AddUser(user));
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+ AddStep("add user", () => MultiplayerClient.AddUser(user));
+ AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2);
- AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
- AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
+ AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3);
+ AddAssert("room has 1 user", () => MultiplayerClient.Room?.Users.Count == 1);
}
[Test]
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
{
int id = 2000;
- AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5);
+ AddRepeatStep("add some users", () => MultiplayerClient.AddUser(new APIUser { Id = id++ }), 5);
checkPlayingUserCount(0);
changeState(3, MultiplayerUserState.WaitingForLoad);
@@ -57,17 +57,17 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
changeState(6, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(6);
- AddStep("another user left", () => Client.RemoveUser((Client.Room?.Users.Last().User).AsNonNull()));
+ AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.Room?.Users.Last().User).AsNonNull()));
checkPlayingUserCount(5);
- AddStep("leave room", () => Client.LeaveRoom());
+ AddStep("leave room", () => MultiplayerClient.LeaveRoom());
checkPlayingUserCount(0);
}
[Test]
public void TestPlayingUsersUpdatedOnJoin()
{
- AddStep("leave room", () => Client.LeaveRoom());
+ AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("create room initially in gameplay", () =>
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
newRoom.CopyFrom(SelectedRoom.Value);
newRoom.RoomID.Value = null;
- Client.RoomSetupAction = room =>
+ MultiplayerClient.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
room.Users.Add(new MultiplayerRoomUser(PLAYER_1_ID)
@@ -94,15 +94,15 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
}
private void checkPlayingUserCount(int expectedCount)
- => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount);
+ => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => MultiplayerClient.CurrentMatchPlayingUserIds.Count == expectedCount);
private void changeState(int userCount, MultiplayerUserState state)
=> AddStep($"{"user".ToQuantity(userCount)} in {state}", () =>
{
for (int i = 0; i < userCount; ++i)
{
- int userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
- Client.ChangeUserState(userId, state);
+ int userId = MultiplayerClient.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
+ MultiplayerClient.ChangeUserState(userId, state);
}
});
}
diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
new file mode 100644
index 0000000000..ae999d08d5
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Osu;
+
+namespace osu.Game.Tests.NonVisual
+{
+ [TestFixture]
+ public class RulesetInfoOrderingTest
+ {
+ [Test]
+ public void TestOrdering()
+ {
+ var rulesets = new[]
+ {
+ new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
+ new OsuRuleset().RulesetInfo,
+ new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
+ new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
+ new CatchRuleset().RulesetInfo,
+ new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
+ };
+
+ var orderedRulesets = rulesets.OrderBy(r => r);
+
+ // Ensure all customs are after official.
+ Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 }));
+
+ // Ensure customs are grouped next to each other (ie. stably sorted).
+ Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2"));
+ Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3"));
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
index e0acc6d8db..41b08a9e98 100644
--- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
@@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual
score.Statistics[HitResult.Good]++;
score.Rank = ScoreRank.X;
+ score.RealmUser.Username = "test";
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
+
+ Assert.That(scoreCopy.RealmUser.Username, Is.Empty);
+ Assert.That(score.RealmUser.Username, Is.EqualTo("test"));
}
[Test]
diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
index 785f31386d..4209f188cc 100644
--- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
+++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
@@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException();
- public ISkin FindProvider(Func lookupFunction) => null;
}
private class TestAnimationTimeReference : IAnimationTimeReference
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 1d639c6418..dd7feb6699 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@@ -12,6 +13,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
+using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
@@ -21,6 +23,8 @@ using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Tests.Resources;
@@ -45,14 +49,33 @@ namespace osu.Game.Tests.Online
[BackgroundDependencyLoader]
private void load(AudioManager audio, GameHost host)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
- Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host));
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API));
}
[SetUp]
public void SetUp() => Schedule(() =>
{
+ ((DummyAPIAccess)API).HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case GetBeatmapsRequest beatmapsReq:
+ var beatmap = CreateAPIBeatmap();
+ beatmap.OnlineID = testBeatmapInfo.OnlineID;
+ beatmap.OnlineBeatmapSetID = testBeatmapSet.OnlineID;
+ beatmap.Checksum = testBeatmapInfo.MD5Hash;
+ beatmap.BeatmapSet!.OnlineID = testBeatmapSet.OnlineID;
+
+ beatmapsReq.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = new List { beatmap } });
+ return true;
+
+ default:
+ return false;
+ }
+ };
+
beatmaps.AllowImport = new TaskCompletionSource();
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
@@ -60,21 +83,38 @@ namespace osu.Game.Tests.Online
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
- ContextFactory.Write(r => r.RemoveAll());
- ContextFactory.Write(r => r.RemoveAll());
+ Realm.Write(r => r.RemoveAll());
+ Realm.Write(r => r.RemoveAll());
- selectedItem.Value = new PlaylistItem
+ selectedItem.Value = new PlaylistItem(testBeatmapInfo)
{
- Beatmap = { Value = testBeatmapInfo },
- Ruleset = { Value = testBeatmapInfo.Ruleset },
+ RulesetID = testBeatmapInfo.Ruleset.OnlineID,
};
- Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
- {
- SelectedItem = { BindTarget = selectedItem, }
- };
+ recreateChildren();
});
+ private void recreateChildren()
+ {
+ var beatmapLookupCache = new BeatmapLookupCache();
+
+ Child = new DependencyProvidingContainer
+ {
+ CachedDependencies = new[]
+ {
+ (typeof(BeatmapLookupCache), (object)beatmapLookupCache)
+ },
+ Children = new Drawable[]
+ {
+ beatmapLookupCache,
+ availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
+ {
+ SelectedItem = { BindTarget = selectedItem, }
+ }
+ }
+ };
+ }
+
[Test]
public void TestBeatmapDownloadingFlow()
{
@@ -91,8 +131,9 @@ namespace osu.Game.Tests.Online
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true);
- addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
+ AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
+ AddAssert("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
+ addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
}
[Test]
@@ -122,10 +163,7 @@ namespace osu.Game.Tests.Online
});
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
- AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
- {
- SelectedItem = { BindTarget = selectedItem }
- });
+ AddStep("recreate tracker", recreateChildren);
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
@@ -164,39 +202,40 @@ namespace osu.Game.Tests.Online
{
public TaskCompletionSource AllowImport = new TaskCompletionSource();
- public Task> CurrentImportTask { get; private set; }
+ public Live CurrentImport { get; private set; }
- public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
- : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
+ public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources,
+ GameHost host = null, WorkingBeatmap defaultBeatmap = null)
+ : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
{
}
- protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
+ protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
{
- return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue);
+ return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue);
}
internal class TestBeatmapModelManager : BeatmapModelManager
{
private readonly TestBeatmapManager testBeatmapManager;
- public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
- : base(databaseContextFactory, storage, beatmapOnlineLookupQueue)
+ public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
+ : base(databaseAccess, storage, beatmapOnlineLookupQueue)
{
this.testBeatmapManager = testBeatmapManager;
}
- public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
- await testBeatmapManager.AllowImport.Task.ConfigureAwait(false);
- return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
+ testBeatmapManager.AllowImport.Task.WaitSafely();
+ return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken));
}
}
}
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{
- public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider, GameHost gameHost)
+ public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider)
: base(importer, apiProvider)
{
}
diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs
index d33081662d..9e7ea02101 100644
--- a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs
+++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs
@@ -3,6 +3,7 @@
using System;
using NUnit.Framework;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
namespace osu.Game.Tests.OnlinePlay
@@ -29,9 +30,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
- new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
- new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
- new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@@ -47,9 +48,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
- new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
- new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
- new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@@ -65,9 +66,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
- new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
- new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
- new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@@ -83,9 +84,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
- new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
- new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
- new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
+ new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 3, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
};
Assert.Multiple(() =>
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index d2cab09ac9..81b624f908 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources
public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null)
{
int j = 0;
- RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo;
+
+ rulesets ??= new[] { new OsuRuleset().RulesetInfo };
+
+ RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
int setId = Interlocked.Increment(ref importId);
diff --git a/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu
new file mode 100644
index 0000000000..23732aef8c
--- /dev/null
+++ b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu
@@ -0,0 +1,3 @@
+[Difficulty]
+OverallDifficulty:1
+ApproachRate:9
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu
new file mode 100644
index 0000000000..18885c6624
--- /dev/null
+++ b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu
@@ -0,0 +1,3 @@
+[Difficulty]
+ApproachRate:9
+OverallDifficulty:1
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/client.db b/osu.Game.Tests/Resources/client.db
new file mode 100644
index 0000000000..079d5af3b7
Binary files /dev/null and b/osu.Game.Tests/Resources/client.db differ
diff --git a/osu.Game.Tests/Resources/undefined-approach-rate.osu b/osu.Game.Tests/Resources/undefined-approach-rate.osu
new file mode 100644
index 0000000000..0de24238bf
--- /dev/null
+++ b/osu.Game.Tests/Resources/undefined-approach-rate.osu
@@ -0,0 +1,2 @@
+[Difficulty]
+OverallDifficulty:1
\ No newline at end of file
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index dd12c94855..8de9f0a292 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
@@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO
public class ImportScoreTest : ImportTest
{
[Test]
- public async Task TestBasicImport()
+ public void TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO
BeatmapInfo = beatmap.Beatmaps.First()
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
@@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportMods()
+ public void TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportStatistics()
+ public void TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -120,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestOnlineScoreIsAvailableLocally()
+ public void TestOnlineScoreIsAvailableLocally()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
- await LoadScoreIntoOsu(osu, new ScoreInfo
+ LoadScoreIntoOsu(osu, new ScoreInfo
{
User = new APIUser { Username = "Test user" },
BeatmapInfo = beatmap.Beatmaps.First(),
@@ -168,13 +167,14 @@ namespace osu.Game.Tests.Scores.IO
}
}
- public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
+ public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
// clone to avoid attaching the input score to realm.
score = score.DeepClone();
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score, archive);
+
+ scoreManager.Import(score, archive);
return scoreManager.Query(_ => true);
}
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 3f063264e0..9b0facd625 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -235,7 +235,7 @@ namespace osu.Game.Tests.Skins.IO
#endregion
- private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu)
+ private void assertCorrectMetadata(Live import1, string name, string creator, OsuGameBase osu)
{
import1.PerformRead(i =>
{
@@ -250,7 +250,7 @@ namespace osu.Game.Tests.Skins.IO
});
}
- private void assertImportedBoth(ILive import1, ILive import2)
+ private void assertImportedBoth(Live import1, Live import2)
{
import1.PerformRead(i1 => import2.PerformRead(i2 =>
{
@@ -260,7 +260,7 @@ namespace osu.Game.Tests.Skins.IO
}));
}
- private void assertImportedOnce(ILive import1, ILive import2)
+ private void assertImportedOnce(Live import1, Live import2)
{
import1.PerformRead(i1 => import2.PerformRead(i2 =>
{
@@ -334,7 +334,7 @@ namespace osu.Game.Tests.Skins.IO
}
}
- private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
+ private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
{
var skinManager = osu.Dependencies.Get();
return await skinManager.Import(archive);
diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs
new file mode 100644
index 0000000000..62e688db90
--- /dev/null
+++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs
@@ -0,0 +1,132 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Utils;
+
+namespace osu.Game.Tests.Utils
+{
+ [TestFixture]
+ public class NamingUtilsTest
+ {
+ [Test]
+ public void TestEmptySet()
+ {
+ string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty(), "New Difficulty");
+
+ Assert.AreEqual("New Difficulty", nextBestName);
+ }
+
+ [Test]
+ public void TestNotTaken()
+ {
+ string[] existingNames =
+ {
+ "Something",
+ "Entirely",
+ "Different"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty", nextBestName);
+ }
+
+ [Test]
+ public void TestNotTakenButClose()
+ {
+ string[] existingNames =
+ {
+ "New Difficulty(1)",
+ "New Difficulty (abcd)",
+ "New Difficulty but not really"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty", nextBestName);
+ }
+
+ [Test]
+ public void TestAlreadyTaken()
+ {
+ string[] existingNames =
+ {
+ "New Difficulty"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty (1)", nextBestName);
+ }
+
+ [Test]
+ public void TestAlreadyTakenWithDifferentCase()
+ {
+ string[] existingNames =
+ {
+ "new difficulty"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty (1)", nextBestName);
+ }
+
+ [Test]
+ public void TestAlreadyTakenWithBrackets()
+ {
+ string[] existingNames =
+ {
+ "new difficulty (copy)"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty (copy)");
+
+ Assert.AreEqual("New Difficulty (copy) (1)", nextBestName);
+ }
+
+ [Test]
+ public void TestMultipleAlreadyTaken()
+ {
+ string[] existingNames =
+ {
+ "New Difficulty",
+ "New difficulty (1)",
+ "new Difficulty (2)",
+ "New DIFFICULTY (3)"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty (4)", nextBestName);
+ }
+
+ [Test]
+ public void TestEvenMoreAlreadyTaken()
+ {
+ string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray();
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty (31)", nextBestName);
+ }
+
+ [Test]
+ public void TestMultipleAlreadyTakenWithGaps()
+ {
+ string[] existingNames =
+ {
+ "New Difficulty",
+ "New Difficulty (1)",
+ "New Difficulty (4)",
+ "New Difficulty (9)"
+ };
+
+ string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
+
+ Assert.AreEqual("New Difficulty (2)", nextBestName);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 4ab4c08353..40e7c0a844 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(Realm);
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index 18572ac211..d4c13059da 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
@@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections
[BackgroundDependencyLoader]
private void load(GameHost host)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
index 243bb71e26..81cb286058 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
@@ -13,6 +13,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit;
+using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
namespace osu.Game.Tests.Visual.Editing
@@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing
base.SetUpSteps();
}
- protected override void LoadEditor()
- {
- Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
- base.LoadEditor();
- }
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
[Test]
public void TestBasicSwitch()
@@ -84,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set target difficulty", () =>
{
targetDifficulty = sameRuleset
- ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
- : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
+ ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName)
+ : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName);
});
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index 2386446e96..ecd4035edd 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -6,14 +6,23 @@ using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Database;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
+using osuTK;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
@@ -39,11 +48,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
}
- protected override void LoadEditor()
- {
- Beatmap.Value = new DummyWorkingBeatmap(Audio, null);
- base.LoadEditor();
- }
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null);
[Test]
public void TestCreateNewBeatmap()
@@ -93,5 +98,245 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
}
+
+ [Test]
+ public void TestCreateNewDifficulty([Values] bool sameRuleset)
+ {
+ string firstDifficultyName = Guid.NewGuid().ToString();
+ string secondDifficultyName = Guid.NewGuid().ToString();
+
+ AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
+ AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
+ {
+ new HitCircle
+ {
+ Position = new Vector2(0),
+ StartTime = 0
+ },
+ new HitCircle
+ {
+ Position = OsuPlayfield.BASE_SIZE,
+ StartTime = 1000
+ }
+ }));
+
+ AddStep("save beatmap", () => Editor.Save());
+ AddAssert("new beatmap persisted", () =>
+ {
+ var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName);
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
+
+ return beatmap != null
+ && beatmap.DifficultyName == firstDifficultyName
+ && set != null
+ && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID);
+ });
+ AddAssert("can save again", () => Editor.Save());
+
+ AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo));
+
+ if (sameRuleset)
+ {
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
+ AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
+ }
+
+ AddUntilStep("wait for created", () =>
+ {
+ string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName != firstDifficultyName;
+ });
+
+ AddAssert("created difficulty has timing point", () =>
+ {
+ var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
+ return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
+ });
+ AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
+
+ AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName);
+ AddStep("save beatmap", () => Editor.Save());
+ AddAssert("new beatmap persisted", () =>
+ {
+ var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName);
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
+
+ return beatmap != null
+ && beatmap.DifficultyName == secondDifficultyName
+ && set != null
+ && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName));
+ });
+ }
+
+ [Test]
+ public void TestCopyDifficulty()
+ {
+ string originalDifficultyName = Guid.NewGuid().ToString();
+ string copyDifficultyName = $"{originalDifficultyName} (copy)";
+
+ AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName);
+ AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
+ {
+ new HitCircle
+ {
+ Position = new Vector2(0),
+ StartTime = 0
+ },
+ new HitCircle
+ {
+ Position = OsuPlayfield.BASE_SIZE,
+ StartTime = 1000
+ }
+ }));
+ AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4);
+ AddStep("set combo colours", () =>
+ {
+ var beatmapSkin = EditorBeatmap.BeatmapSkin.AsNonNull();
+ beatmapSkin.ComboColours.Clear();
+ beatmapSkin.ComboColours.AddRange(new[]
+ {
+ new Colour4(255, 0, 0, 255),
+ new Colour4(0, 0, 255, 255)
+ });
+ });
+ AddStep("set status & online ID", () =>
+ {
+ EditorBeatmap.BeatmapInfo.OnlineID = 123456;
+ EditorBeatmap.BeatmapInfo.Status = BeatmapOnlineStatus.WIP;
+ });
+
+ AddStep("save beatmap", () => Editor.Save());
+ AddAssert("new beatmap persisted", () =>
+ {
+ var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName);
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
+
+ return beatmap != null
+ && beatmap.DifficultyName == originalDifficultyName
+ && set != null
+ && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID);
+ });
+ AddAssert("can save again", () => Editor.Save());
+
+ AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
+
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
+ AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick());
+
+ AddUntilStep("wait for created", () =>
+ {
+ string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName != originalDifficultyName;
+ });
+
+ AddAssert("created difficulty has copy suffix in name", () => EditorBeatmap.BeatmapInfo.DifficultyName == copyDifficultyName);
+ AddAssert("created difficulty has timing point", () =>
+ {
+ var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
+ return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
+ });
+ AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2);
+ AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
+ AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);
+
+ AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None);
+ AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1);
+
+ AddStep("save beatmap", () => Editor.Save());
+
+ BeatmapInfo refetchedBeatmap = null;
+ Live refetchedBeatmapSet = null;
+
+ AddStep("refetch from database", () =>
+ {
+ refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName);
+ refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
+ });
+
+ AddAssert("new beatmap persisted", () =>
+ {
+ return refetchedBeatmap != null
+ && refetchedBeatmap.DifficultyName == copyDifficultyName
+ && refetchedBeatmapSet != null
+ && refetchedBeatmapSet.PerformRead(s =>
+ s.Beatmaps.Count == 2
+ && s.Beatmaps.Any(b => b.DifficultyName == originalDifficultyName)
+ && s.Beatmaps.Any(b => b.DifficultyName == copyDifficultyName));
+ });
+ AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2));
+ }
+
+ [Test]
+ public void TestCreateMultipleNewDifficultiesSucceeds()
+ {
+ Guid setId = Guid.Empty;
+
+ AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
+ AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "New Difficulty");
+ AddStep("save beatmap", () => Editor.Save());
+ AddAssert("new beatmap persisted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
+ });
+
+ AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
+ AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
+
+ AddUntilStep("wait for created", () =>
+ {
+ string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName != "New Difficulty";
+ });
+ AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)");
+ AddAssert("new difficulty persisted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
+ });
+ }
+
+ [Test]
+ public void TestSavingBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset)
+ {
+ Guid setId = Guid.Empty;
+ const string duplicate_difficulty_name = "duplicate";
+
+ AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
+ AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name);
+ AddStep("save beatmap", () => Editor.Save());
+ AddAssert("new beatmap persisted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
+ });
+
+ AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo));
+
+ if (sameRuleset)
+ {
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
+ AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
+ }
+
+ AddUntilStep("wait for created", () =>
+ {
+ string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName != duplicate_difficulty_name;
+ });
+
+ AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name);
+ AddStep("try to save beatmap", () => Editor.Save());
+ AddAssert("beatmap set not corrupted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ // the difficulty was already created at the point of the switch.
+ // what we want to check is that both difficulties do not use the same file.
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index f89be0adf3..adaa24d542 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -3,92 +3,139 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Input;
+using osu.Framework.Allocation;
+using osu.Framework.Screens;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Setup;
-using osu.Game.Screens.Menu;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Select;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
- public class TestSceneEditorSaving : OsuGameTestScene
+ public class TestSceneEditorSaving : EditorSavingTestScene
{
- private Editor editor => Game.ChildrenOfType().FirstOrDefault();
-
- private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
-
- ///
- /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
- ///
[Test]
- public void TestNewBeatmapSaveThenLoad()
+ public void TestMetadata()
{
- AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
-
- PushAndConfirm(() => new EditorLoader());
-
- AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
-
- AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
-
- AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
- AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
AddStep("Set artist and title", () =>
{
- editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
- editorBeatmap.BeatmapInfo.Metadata.Title = "title";
+ EditorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
+ EditorBeatmap.BeatmapInfo.Metadata.Title = "title";
});
- AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
+ AddStep("Set author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username = "author");
+ AddStep("Set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
- AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
+ SaveEditor();
+ AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
+ AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
+ AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
+ AddAssert("Beatmap has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
+ AddAssert("Beatmap still has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
+ AddAssert("Beatmap still has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
+ AddAssert("Beatmap still has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
+ }
+
+ [Test]
+ public void TestConfiguration()
+ {
+ double originalTimelineZoom = 0;
+ double changedTimelineZoom = 0;
+
+ AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16);
+ AddStep("Set timeline zoom", () =>
+ {
+ originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
+
+ var timeline = Editor.ChildrenOfType().Single();
+ InputManager.MoveMouseTo(timeline);
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.ScrollVerticalBy(15f);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ AddAssert("Ensure timeline zoom changed", () =>
+ {
+ changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
+ return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom);
+ });
+
+ SaveEditor();
+
+ AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
+ AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
+ AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
+ }
+
+ [Test]
+ public void TestDifficulty()
+ {
+ AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7);
+
+ SaveEditor();
+
+ AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
+ }
+
+ [Test]
+ public void TestHitObjectPlacement()
+ {
+ AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
- checkMutations();
+ SaveEditor();
+
+ AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
// After placement these must be non-default as defaults are read-only.
AddAssert("Placed object has non-default control points", () =>
- editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
- editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
+ EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
+ EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
- AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
+ ReloadEditorToSameBeatmap();
- checkMutations();
+ AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
- AddStep("Exit", () => InputManager.Key(Key.Escape));
-
- AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
-
- Screens.Select.SongSelect songSelect = null;
-
- PushAndConfirm(() => songSelect = new PlaySongSelect());
- AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
-
- AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
- AddStep("Open options", () => InputManager.Key(Key.F3));
- AddStep("Enter editor", () => InputManager.Key(Key.Number5));
-
- AddUntilStep("Wait for editor load", () => editor != null);
-
- checkMutations();
+ // After placement these must be non-default as defaults are read-only.
+ AddAssert("Placed object still has non-default control points", () =>
+ EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
+ EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
}
- private void checkMutations()
+ [Test]
+ public void TestExitWithoutSaveFromExistingBeatmap()
{
- AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
- AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
- AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
- AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
+ const string tags_to_save = "these tags will be saved";
+ const string tags_to_discard = "these tags should be discarded";
+
+ AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save);
+ SaveEditor();
+ AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
+
+ ReloadEditorToSameBeatmap();
+ AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
+ AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard);
+
+ AddStep("Exit editor", () => Editor.Exit());
+ AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
+ AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
}
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
index bb630e5d5c..79ea866efe 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
@@ -17,6 +17,7 @@ using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.GameplayTest;
using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Graphics;
using osuTK.Input;
@@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing
base.SetUpSteps();
}
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0));
+
protected override void LoadEditor()
{
- Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
SelectedMods.Value = new[] { new ModCinema() };
base.LoadEditor();
}
@@ -67,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
- var background = this.ChildrenOfType().Single();
+ // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
+ // due to the beatmap refetch logic ran on editor suspend.
+ // this test cares about checking the background belonging to the editor specifically, so check that using reference equality
+ // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
+ var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
@@ -96,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
- var background = this.ChildrenOfType().Single();
+ // the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
+ // due to the beatmap refetch logic ran on editor suspend.
+ // this test cares about checking the background belonging to the editor specifically, so check that using reference equality
+ // (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
+ var background = this.ChildrenOfType().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs
new file mode 100644
index 0000000000..b34974dfc7
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs
@@ -0,0 +1,88 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens.Edit.Timing;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene
+ {
+ private LabelledTimeSignature timeSignature;
+
+ private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () =>
+ {
+ Child = timeSignature = new LabelledTimeSignature
+ {
+ Label = "Time Signature",
+ RelativeSizeAxes = Axes.None,
+ Width = 400,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { Value = initial }
+ };
+ });
+
+ private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType().Single();
+
+ [Test]
+ public void TestInitialValue()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleTriple);
+ AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
+ }
+
+ [Test]
+ public void TestChangeViaCurrent()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5));
+
+ AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5)));
+ AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5");
+
+ AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple);
+
+ AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
+ AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3");
+ }
+
+ [Test]
+ public void TestChangeNumerator()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
+
+ AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("drop focus", () => InputManager.ChangeFocus(null));
+ AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
+ }
+
+ [Test]
+ public void TestInvalidChangeRollbackOnCommit()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
+
+ AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("drop focus", () => InputManager.ChangeFocus(null));
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+ AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
index 2544b6c2a1..81ab4712ab 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
@@ -47,25 +47,25 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
- new HitCircle { StartTime = 100 },
- new HitCircle { StartTime = 200, Position = new Vector2(100) },
- new HitCircle { StartTime = 300, Position = new Vector2(200) },
- new HitCircle { StartTime = 400, Position = new Vector2(300) },
+ new HitCircle { StartTime = 500 },
+ new HitCircle { StartTime = 1000, Position = new Vector2(100) },
+ new HitCircle { StartTime = 1500, Position = new Vector2(200) },
+ new HitCircle { StartTime = 2000, Position = new Vector2(300) },
}));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
AddStep("nudge forwards", () => InputManager.Key(Key.K));
- AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100);
+ AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 500);
AddStep("nudge backwards", () => InputManager.Key(Key.J));
- AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
+ AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 500);
}
[Test]
public void TestBasicSelect()
{
- var addedObject = new HitCircle { StartTime = 100 };
+ var addedObject = new HitCircle { StartTime = 500 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Editing
var addedObject2 = new HitCircle
{
- StartTime = 200,
+ StartTime = 1000,
Position = new Vector2(100),
};
@@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.Editing
{
var addedObjects = new[]
{
- new HitCircle { StartTime = 100 },
- new HitCircle { StartTime = 200, Position = new Vector2(100) },
- new HitCircle { StartTime = 300, Position = new Vector2(200) },
- new HitCircle { StartTime = 400, Position = new Vector2(300) },
+ new HitCircle { StartTime = 500 },
+ new HitCircle { StartTime = 1000, Position = new Vector2(100) },
+ new HitCircle { StartTime = 1500, Position = new Vector2(200) },
+ new HitCircle { StartTime = 2000, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
@@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestBasicDeselect()
{
- var addedObject = new HitCircle { StartTime = 100 };
+ var addedObject = new HitCircle { StartTime = 500 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
@@ -166,11 +166,11 @@ namespace osu.Game.Tests.Visual.Editing
{
var addedObjects = new[]
{
- new HitCircle { StartTime = 100 },
- new HitCircle { StartTime = 200, Position = new Vector2(100) },
- new HitCircle { StartTime = 300, Position = new Vector2(200) },
- new HitCircle { StartTime = 400, Position = new Vector2(300) },
- new HitCircle { StartTime = 500, Position = new Vector2(400) },
+ new HitCircle { StartTime = 500 },
+ new HitCircle { StartTime = 1000, Position = new Vector2(100) },
+ new HitCircle { StartTime = 1500, Position = new Vector2(200) },
+ new HitCircle { StartTime = 2000, Position = new Vector2(300) },
+ new HitCircle { StartTime = 2500, Position = new Vector2(400) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
@@ -236,10 +236,10 @@ namespace osu.Game.Tests.Visual.Editing
{
var addedObjects = new[]
{
- new HitCircle { StartTime = 100 },
- new HitCircle { StartTime = 200, Position = new Vector2(100) },
- new HitCircle { StartTime = 300, Position = new Vector2(200) },
- new HitCircle { StartTime = 400, Position = new Vector2(300) },
+ new HitCircle { StartTime = 500 },
+ new HitCircle { StartTime = 1000, Position = new Vector2(100) },
+ new HitCircle { StartTime = 1500, Position = new Vector2(200) },
+ new HitCircle { StartTime = 2000, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index 7167d3120a..744227c55e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
// The pause screen and fail animation both ramp frequency.
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index fa27e1abdd..6430c29dfa 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1);
AddAssert("total number of results == 1", () =>
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
index 235842acc9..ddb0872541 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
@@ -3,9 +3,9 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osuTK.Input;
@@ -19,28 +19,35 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
- [BackgroundDependencyLoader]
- private void load()
+ private HoldForMenuButton holdForMenuButton;
+
+ [SetUpSteps]
+ public void SetUpSteps()
{
- HoldForMenuButton holdForMenuButton;
-
- Add(holdForMenuButton = new HoldForMenuButton
+ AddStep("create button", () =>
{
- Origin = Anchor.BottomRight,
- Anchor = Anchor.BottomRight,
- Action = () => exitAction = true
+ exitAction = false;
+
+ Child = holdForMenuButton = new HoldForMenuButton
+ {
+ Scale = new Vector2(2),
+ Origin = Anchor.CentreRight,
+ Anchor = Anchor.CentreRight,
+ Action = () => exitAction = true
+ };
});
+ }
- var text = holdForMenuButton.Children.OfType().First();
-
+ [Test]
+ public void TestMovementAndTrigger()
+ {
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
- AddUntilStep("Text visible", () => text.IsPresent && !exitAction);
+ AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction);
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
- AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction);
+ AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction);
AddStep("Trigger exit action", () =>
{
- exitAction = false;
InputManager.MoveMouseTo(holdForMenuButton);
InputManager.PressButton(MouseButton.Left);
});
@@ -50,6 +57,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction);
+ AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left));
}
+
+ [Test]
+ public void TestFadeOnNoInput()
+ {
+ AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One));
+ AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent);
+ AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f);
+ }
+
+ private SpriteText getSpriteText() => holdForMenuButton.Children.OfType().First();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
index 951ee1489d..759e4fa4ec 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
@@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Add(new ModNightcore.NightcoreBeatContainer());
- AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
- AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
+ AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple));
+ AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple));
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 04676f656f..ea0255ab76 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestPauseAfterFail()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);
@@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExitFromFailedGameplayAfterFailAnimation()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);
@@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExitFromFailedGameplayDuringFailAnimation()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
// will finish the fail animation and show the fail/pause screen.
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
@@ -227,7 +227,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestQuickRetryFromFailedGameplay()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
confirmExited();
@@ -236,7 +236,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestQuickExitFromFailedGameplay()
{
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
confirmExited();
@@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
confirmClockRunning(false);
confirmNotExited();
- AddAssert("player not failed", () => !Player.HasFailed);
+ AddAssert("player not failed", () => !Player.GameplayState.HasFailed);
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index a4a4f351ec..58b5df2612 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
- AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID);
+ AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new TaikoRuleset().RulesetInfo.ShortName);
}
[Test]
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
- AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID);
+ AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new ManiaRuleset().RulesetInfo.ShortName);
}
[Test]
@@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("exit", () => Player.Exit());
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
@@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay
addFakeHit();
- AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("exit", () => Player.Exit());
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 8199389b36..8b7e1c4e58 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestScoreImportThenDelete()
{
- ILive imported = null;
+ Live imported = null;
AddStep("create button without replay", () =>
{
@@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
- AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely());
+ AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)));
AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 4eab1a21da..8df32c500e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
- public override void CollectPendingInputs(List inputs)
+ protected override void CollectReplayInputs(List inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 3e8ba69e01..35130f3109 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -301,8 +301,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig