diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000000..6ba6ae82c8
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,18 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "cake.tool": {
+ "version": "0.35.0",
+ "commands": [
+ "dotnet-cake"
+ ]
+ },
+ "dotnet-format": {
+ "version": "3.1.37601",
+ "commands": [
+ "dotnet-format"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 0dd7ef8ed1..b5333ad8e7 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -12,16 +12,183 @@ trim_trailing_whitespace = true
#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
-dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate
-dotnet_naming_rule.public_members_pascalcase.severity = suggestion
+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
+
dotnet_naming_symbols.private_members.applicable_accessibilities = private
-dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate
-dotnet_naming_rule.private_members_camelcase.severity = suggestion
+dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.private_members_camelcase.severity = warning
dotnet_naming_rule.private_members_camelcase.symbols = private_members
-dotnet_naming_rule.private_members_camelcase.style = camelcase
\ No newline at end of file
+dotnet_naming_rule.private_members_camelcase.style = camelcase
+
+dotnet_naming_symbols.local_function.applicable_kinds = local_function
+dotnet_naming_rule.local_function_camelcase.severity = warning
+dotnet_naming_rule.local_function_camelcase.symbols = local_function
+dotnet_naming_rule.local_function_camelcase.style = camelcase
+
+#all_lower for private and local constants/static readonlys
+dotnet_naming_style.all_lower.capitalization = all_lower
+dotnet_naming_style.all_lower.word_separator = _
+
+dotnet_naming_symbols.private_constants.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants.required_modifiers = const
+dotnet_naming_symbols.private_constants.applicable_kinds = field
+dotnet_naming_rule.private_const_all_lower.severity = warning
+dotnet_naming_rule.private_const_all_lower.symbols = private_constants
+dotnet_naming_rule.private_const_all_lower.style = all_lower
+
+dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
+dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
+dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
+dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+dotnet_naming_rule.local_const_all_lower.severity = warning
+dotnet_naming_rule.local_const_all_lower.symbols = local_constants
+dotnet_naming_rule.local_const_all_lower.style = all_lower
+
+#ALL_UPPER for non private constants/static readonlys
+dotnet_naming_style.all_upper.capitalization = all_upper
+dotnet_naming_style.all_upper.word_separator = _
+
+dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_constants.required_modifiers = const
+dotnet_naming_symbols.public_constants.applicable_kinds = field
+dotnet_naming_rule.public_const_all_upper.severity = warning
+dotnet_naming_rule.public_const_all_upper.symbols = public_constants
+dotnet_naming_rule.public_const_all_upper.style = all_upper
+
+dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
+dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
+dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
+dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
+
+#Roslyn formating options
+
+#Formatting - indentation options
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_open_brace = all
+#csharp_new_line_before_members_in_anonymous_types = true
+#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
+csharp_new_line_between_query_expression_clauses = true
+
+#Formatting - organize using options
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#Roslyn language styles
+
+#Style - this. qualification
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+#Style - type names
+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_elsewhere = true:silent
+
+#Style - modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
+
+#Style - parentheses
+# Skipped because roslyn cannot separate +-*/ with << >>
+
+#Style - expression bodies
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_methods = true:silent
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_local_functions = true:silent
+
+#Style - expression preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_compound_assignment = true:warning
+
+#Style - null/type checks
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_throw_expression = true:silent
+csharp_style_conditional_delegate_call = true:warning
+
+#Style - unused
+dotnet_style_readonly_field = true:silent
+dotnet_code_quality_unused_parameters = non_public:silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+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
+
+#Style - other C# 7.x features
+dotnet_style_prefer_inferred_tuple_names = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+
+#Style - C# 8 features
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:silent
+csharp_style_prefer_index_operator = false:none
+csharp_style_prefer_range_operator = false:none
+csharp_style_prefer_switch_expression = false:none
+
+#Supressing roslyn built-in analyzers
+# Suppress: EC112
+
+#Private method is unused
+dotnet_diagnostic.IDE0051.severity = silent
+#Private member is unused
+dotnet_diagnostic.IDE0052.severity = silent
+
+#Rules for disposable
+dotnet_diagnostic.IDE0067.severity = none
+dotnet_diagnostic.IDE0068.severity = none
+dotnet_diagnostic.IDE0069.severity = none
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e60058ab35..e6b5db5904 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,14 +10,8 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
-### Cake ###
-tools/**
-build/tools/**
-
-fastlane/report.xml
-
# Build results
-bin/[Dd]ebug/
+[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
@@ -104,7 +98,6 @@ $tf/
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-inspectcode
# JustCode is a .NET coding add-in
.JustCode
@@ -254,20 +247,87 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/.idea.osu/.idea/*.xml
-.idea/.idea.osu/.idea/codeStyles/*.xml
-.idea/.idea.osu/.idea/dataSources/*.xml
-.idea/.idea.osu/.idea/dictionaries/*.xml
-.idea/.idea.osu/*.iml
-*.sln.iml
-
-# CodeRush
-.cr/
-
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-Staging/
+# Cake #
+/tools/**
+/build/tools/**
+/build/temp/**
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# 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
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/.idea/.idea.osu.Desktop/.idea/.name b/.idea/.idea.osu.Desktop/.idea/.name
new file mode 100644
index 0000000000..12bf4aebba
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/.name
@@ -0,0 +1 @@
+osu.Desktop
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000..a55e7a179b
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/dataSources.xml b/.idea/.idea.osu.Desktop/.idea/dataSources.xml
new file mode 100644
index 0000000000..10f8c1c84d
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/dataSources.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$USER_HOME$/.local/share/osu/client.db
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/encodings.xml b/.idea/.idea.osu.Desktop/.idea/encodings.xml
new file mode 100644
index 0000000000..15a15b218a
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml
new file mode 100644
index 0000000000..27ba142e96
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/misc.xml b/.idea/.idea.osu.Desktop/.idea/misc.xml
new file mode 100644
index 0000000000..1d8c84d0af
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml
new file mode 100644
index 0000000000..fe63f5faf3
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml
new file mode 100644
index 0000000000..7515e76054
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/Tournament.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/osu_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
similarity index 100%
rename from .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
diff --git a/.idea/.idea.osu.Desktop/.idea/vcs.xml b/.idea/.idea.osu.Desktop/.idea/vcs.xml
new file mode 100644
index 0000000000..3de04b744c
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/vcs.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/indexLayout.xml b/.idea/.idea.osu/.idea/indexLayout.xml
new file mode 100644
index 0000000000..27ba142e96
--- /dev/null
+++ b/.idea/.idea.osu/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/modules.xml b/.idea/.idea.osu/.idea/modules.xml
new file mode 100644
index 0000000000..0360fdbc5e
--- /dev/null
+++ b/.idea/.idea.osu/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
new file mode 100644
index 0000000000..7515e76054
--- /dev/null
+++ b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/vcs.xml b/.idea/.idea.osu/.idea/vcs.xml
new file mode 100644
index 0000000000..94a25f7f4c
--- /dev/null
+++ b/.idea/.idea.osu/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 70e620bca2..04ff7c1bea 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -100,7 +100,7 @@
"command": "dotnet",
"args": [
"restore",
- "osu.sln"
+ "build/Desktop.proj"
],
"problemMatcher": []
}
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
new file mode 100644
index 0000000000..5d86b99fd7
--- /dev/null
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -0,0 +1,5 @@
+M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead.
+M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead.
+M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead.
+T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
+M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..3f030e4146
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,40 @@
+
+
+
+ 8.0
+
+
+ $(MSBuildThisFileDirectory)app.manifest
+
+
+
+ osu.licenseheader
+
+
+
+
+
+
+
+
+
+
+ true
+ $(NoWarn);CS1591
+
+
+
+ $(NoWarn);NU1701
+
+
+ ppy Pty Ltd
+ MIT
+ https://github.com/ppy/osu
+ https://github.com/ppy/osu
+ Automated release.
+ ppy Pty Ltd
+ Copyright (c) 2019 ppy Pty Ltd
+ osu game
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 0460e9cbcf..65fb97eb5d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,10 @@
# osu!
-[](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
+[](https://ci.appveyor.com/project/peppy/osu)
+[]()
+[](https://www.codefactor.io/repository/github/ppy/osu)
+[](https://discord.gg/ppy)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
@@ -19,9 +22,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
-- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
+- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
-- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
+- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
## Running osu!
@@ -57,7 +60,8 @@ git pull
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
-> Visual Studio Code users must run the `Restore` task before any build attempt.
+- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations.
+- Visual Studio Code users must run the `Restore` task before any build attempt.
You can also build and run osu! from the command-line with a single command:
@@ -67,19 +71,7 @@ dotnet run --project osu.Desktop
If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
-If the build fails, try to restore nuget packages with `dotnet restore`.
-
-#### A note for Linux users
-
-On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`.
-
-`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`.
-
-For example, you can run osu! with the following command:
-
-```shell
-LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop
-```
+If the build fails, try to restore NuGet packages with `dotnet restore`.
### Testing with resource/framework modifications
@@ -87,11 +79,11 @@ Sometimes it may be necessary to cross-test changes in [osu-resources](https://g
### Code analysis
-Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice.
+Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## Contributing
-We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
+We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
@@ -99,7 +91,7 @@ Before starting, please make sure you are familiar with the [development and tes
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
-For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
+For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
## Licence
diff --git a/appveyor.yml b/appveyor.yml
index f59c0b162d..f911d67c6e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,6 +1,6 @@
clone_depth: 1
version: '{branch}-{build}'
-image: Visual Studio 2019 Preview
+image: Visual Studio 2019
test: off
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
index 13635b943c..fb7825b31d 100644
--- a/appveyor_deploy.yml
+++ b/appveyor_deploy.yml
@@ -1,6 +1,6 @@
clone_depth: 1
version: '{build}'
-image: Visual Studio 2019 Preview
+image: Visual Studio 2019
test: off
skip_non_tags: true
build_script:
diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png
new file mode 100644
index 0000000000..c2a587fdc2
Binary files /dev/null and b/assets/lazer-nuget.png differ
diff --git a/build.ps1 b/build.ps1
index 2dbd10a150..4b3b1f717a 100755
--- a/build.ps1
+++ b/build.ps1
@@ -21,7 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" }
if ($Experimental) { $cakeArguments += "-experimental" }
$cakeArguments += $ScriptArgs
-dotnet tool install Cake.Tool --global --version 0.35.0
+dotnet tool restore
dotnet cake ./build/build.cake --bootstrap
dotnet cake ./build/build.cake $cakeArguments
exit $LASTEXITCODE
\ No newline at end of file
diff --git a/build.sh b/build.sh
index ac6bd877a6..2c22f08574 100755
--- a/build.sh
+++ b/build.sh
@@ -1,5 +1,5 @@
echo "Installing Cake.Tool..."
-dotnet tool install Cake.Tool --global --version 0.35.0
+dotnet tool restore
# Parse arguments.
CAKE_ARGUMENTS=()
diff --git a/build/Desktop.proj b/build/Desktop.proj
new file mode 100644
index 0000000000..b1c6b065e8
--- /dev/null
+++ b/build/Desktop.proj
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/build.cake b/build/build.cake
index cfdfebee61..274e57ef4e 100644
--- a/build/build.cake
+++ b/build/build.cake
@@ -11,7 +11,9 @@ var target = Argument("target", "Build");
var configuration = Argument("configuration", "Release");
var rootDirectory = new DirectoryPath("..");
-var solution = rootDirectory.CombineWithFilePath("osu.sln");
+var sln = rootDirectory.CombineWithFilePath("osu.sln");
+var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj");
+var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
///////////////////////////////////////////////////////////////////////////////
// TASKS
@@ -19,7 +21,7 @@ var solution = rootDirectory.CombineWithFilePath("osu.sln");
Task("Compile")
.Does(() => {
- DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings {
+ DotNetCoreBuild(desktopBuilds.FullPath, new DotNetCoreBuildSettings {
Configuration = configuration,
});
});
@@ -41,7 +43,7 @@ Task("InspectCode")
.WithCriteria(IsRunningOnWindows())
.IsDependentOn("Compile")
.Does(() => {
- InspectCode(solution, new InspectCodeSettings {
+ InspectCode(desktopSlnf, new InspectCodeSettings {
CachesHome = "inspectcode",
OutputFile = "inspectcodereport.xml",
});
@@ -59,8 +61,12 @@ Task("CodeFileSanity")
});
});
+Task("DotnetFormat")
+ .Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check"));
+
Task("Build")
.IsDependentOn("CodeFileSanity")
+ .IsDependentOn("DotnetFormat")
.IsDependentOn("InspectCode")
.IsDependentOn("Test");
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 7adf42a1eb..28a83fbbae 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -49,12 +49,12 @@ desc 'Deploy to play store'
desc 'Compile the project'
lane :build do |options|
nuget_restore(
- project_path: 'osu.Android.sln'
+ project_path: 'osu.sln'
)
souyuz(
build_configuration: 'Release',
- solution_path: 'osu.Android.sln',
+ solution_path: 'osu.sln',
platform: "android",
output_path: "osu.Android/bin/Release/",
keystore_path: options[:keystore_path],
@@ -70,7 +70,7 @@ desc 'Deploy to play store'
android_build = split.join('')
app_version(
- solution_path: 'osu.Android.sln',
+ solution_path: 'osu.sln',
version: options[:version],
build: android_build,
)
@@ -106,7 +106,7 @@ platform :ios do
desc 'Compile the project'
lane :build do
nuget_restore(
- project_path: 'osu.iOS.sln'
+ project_path: 'osu.sln'
)
souyuz(
diff --git a/global.json b/global.json
new file mode 100644
index 0000000000..43bb34912a
--- /dev/null
+++ b/global.json
@@ -0,0 +1,5 @@
+{
+ "msbuild-sdks": {
+ "Microsoft.Build.Traversal": "2.0.24"
+ }
+}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 8b31be3f12..efaf7241cb 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -1,51 +1,43 @@
- Debug
- AnyCPU
+ 8.0
bin\$(Configuration)
4
2.0
false
false
- default
Library
512
Off
True
Xamarin.Android.Net.AndroidClientHandler
- v9.0
+ v10.0
false
+ true
+ armeabi-v7a;x86;arm64-v8a
+ true
+ cjk,mideast,other,rare,west
+ SdkOnly
+ prompt
-
+
True
portable
False
DEBUG;TRACE
- prompt
false
false
- SdkOnly
true
false
- cjk,mideast,other,rare,west
- true
- armeabi-v7a;x86;arm64-v8a
- true
-
+
false
None
True
- prompt
true
false
- SdkOnly
False
true
- cjk,mideast,other,rare,west
- true
- armeabi-v7a;x86;arm64-v8a
- true
@@ -62,6 +54,6 @@
-
+
diff --git a/osu.Android.sln b/osu.Android.sln
deleted file mode 100644
index ebf2c55cb4..0000000000
--- a/osu.Android.sln
+++ /dev/null
@@ -1,126 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28516.95
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Android", "osu.Android\osu.Android.csproj", "{D1D5F9A8-B40B-40E6-B02F-482D03346D3D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.Android", "osu.Game.Rulesets.Catch.Tests.Android\osu.Game.Rulesets.Catch.Tests.Android.csproj", "{C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.Android", "osu.Game.Rulesets.Mania.Tests.Android\osu.Game.Rulesets.Mania.Tests.Android.csproj", "{531F1092-DB27-445D-AA33-2A77C7187C99}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.Android", "osu.Game.Rulesets.Osu.Tests.Android\osu.Game.Rulesets.Osu.Tests.Android.csproj", "{90CAB706-39CB-4B93-9629-3218A6FF8E9B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.Android", "osu.Game.Rulesets.Taiko.Tests.Android\osu.Game.Rulesets.Taiko.Tests.Android.csproj", "{3701A0A1-8476-42C6-B5C4-D24129B4A484}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.Android", "osu.Game.Tests.Android\osu.Game.Tests.Android.csproj", "{5CC222DC-5716-4499-B897-DCBDDA4A5CF9}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
- {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU
- {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU
- {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU
- {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
- EndGlobalSection
- GlobalSection(MonoDevelopProperties) = preSolution
- Policies = $0
- $0.TextStylePolicy = $1
- $1.EolMarker = Windows
- $1.inheritsSet = VisualStudio
- $1.inheritsScope = text/plain
- $1.scope = text/x-csharp
- $0.CSharpFormattingPolicy = $2
- $2.IndentSwitchSection = True
- $2.NewLinesForBracesInProperties = True
- $2.NewLinesForBracesInAccessors = True
- $2.NewLinesForBracesInAnonymousMethods = True
- $2.NewLinesForBracesInControlBlocks = True
- $2.NewLinesForBracesInAnonymousTypes = True
- $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
- $2.NewLinesForBracesInLambdaExpressionBody = True
- $2.NewLineForElse = True
- $2.NewLineForCatch = True
- $2.NewLineForFinally = True
- $2.NewLineForMembersInObjectInit = True
- $2.NewLineForMembersInAnonymousTypes = True
- $2.NewLineForClausesInQuery = True
- $2.SpacingAfterMethodDeclarationName = False
- $2.SpaceAfterMethodCallName = False
- $2.SpaceBeforeOpenSquareBracket = False
- $2.inheritsSet = Mono
- $2.inheritsScope = text/x-csharp
- $2.scope = text/x-csharp
- EndGlobalSection
-EndGlobal
diff --git a/osu.Android.sln.DotSettings b/osu.Android.sln.DotSettings
deleted file mode 100644
index 5a97fc7518..0000000000
--- a/osu.Android.sln.DotSettings
+++ /dev/null
@@ -1,834 +0,0 @@
-
- True
- True
- True
- True
- ExplicitlyExcluded
- ExplicitlyExcluded
- SOLUTION
- HINT
- WARNING
-
- True
- WARNING
- WARNING
- HINT
- HINT
- HINT
- HINT
- WARNING
- WARNING
- WARNING
- HINT
- WARNING
- HINT
- SUGGESTION
- HINT
- HINT
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- WARNING
- WARNING
- HINT
- WARNING
- WARNING
- DO_NOT_SHOW
- HINT
- WARNING
- DO_NOT_SHOW
- WARNING
- HINT
- HINT
- HINT
- ERROR
- HINT
- HINT
- HINT
- WARNING
- WARNING
- HINT
- DO_NOT_SHOW
- HINT
- HINT
- HINT
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- HINT
- HINT
- HINT
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- DO_NOT_SHOW
- DO_NOT_SHOW
- DO_NOT_SHOW
- WARNING
-
- WARNING
- WARNING
- WARNING
- ERROR
- WARNING
- WARNING
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- DO_NOT_SHOW
- DO_NOT_SHOW
- DO_NOT_SHOW
- WARNING
- WARNING
- HINT
- WARNING
- HINT
- HINT
- HINT
- HINT
- HINT
- HINT
- HINT
-
- HINT
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- HINT
- WARNING
- WARNING
- HINT
- HINT
- WARNING
- <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile>
- Code Cleanup (peppy)
- Required
- Required
- Required
- Explicit
- ExpressionBody
- ExpressionBody
- True
- NEXT_LINE
- True
- True
- True
- True
- True
- True
- True
- True
- NEXT_LINE
- 1
- 1
- NEXT_LINE
- MULTILINE
- NEXT_LINE
- 1
- 1
- True
- NEXT_LINE
- NEVER
- NEVER
- True
- False
- True
- NEVER
- False
- False
- True
- False
- False
- True
- True
- False
- False
- CHOP_IF_LONG
- True
- 200
- CHOP_IF_LONG
- False
- False
- AABB
- API
- BPM
- GC
- GL
- GLSL
- HID
- HUD
- ID
- IP
- IPC
- LTRB
- MD5
- NS
- OS
- RGB
- RNG
- SHA
- SRGB
- TK
- SS
- PP
- GMT
- QAT
- BNG
- UI
- HINT
- <?xml version="1.0" encoding="utf-16"?>
-<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
- <TypePattern DisplayName="COM interfaces or structs">
- <TypePattern.Match>
- <Or>
- <And>
- <Kind Is="Interface" />
- <Or>
- <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
- <HasAttribute Name="System.Runtime.InteropServices.ComImport" />
- </Or>
- </And>
- <Kind Is="Struct" />
- </Or>
- </TypePattern.Match>
- </TypePattern>
- <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
- <TypePattern.Match>
- <And>
- <Kind Is="Class" />
- <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" />
- </And>
- </TypePattern.Match>
- <Entry DisplayName="Setup/Teardown Methods">
- <Entry.Match>
- <And>
- <Kind Is="Method" />
- <Or>
- <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" />
- <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" />
- <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" />
- <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" />
- </Or>
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="All other members" />
- <Entry Priority="100" DisplayName="Test Methods">
- <Entry.Match>
- <And>
- <Kind Is="Method" />
- <HasAttribute Name="NUnit.Framework.TestAttribute" />
- </And>
- </Entry.Match>
- <Entry.SortBy>
- <Name />
- </Entry.SortBy>
- </Entry>
- </TypePattern>
- <TypePattern DisplayName="Default Pattern">
- <Group DisplayName="Fields/Properties">
- <Group DisplayName="Public Fields">
- <Entry DisplayName="Constant Fields">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Or>
- <Kind Is="Constant" />
- <Readonly />
- <And>
- <Static />
- <Readonly />
- </And>
- </Or>
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Static Fields">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Static />
- <Not>
- <Readonly />
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Normal Fields">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Not>
- <Or>
- <Static />
- <Readonly />
- </Or>
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Entry DisplayName="Public Properties">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Kind Is="Property" />
- </And>
- </Entry.Match>
- </Entry>
- <Group DisplayName="Internal Fields">
- <Entry DisplayName="Constant Fields">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Or>
- <Kind Is="Constant" />
- <Readonly />
- <And>
- <Static />
- <Readonly />
- </And>
- </Or>
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Static Fields">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Static />
- <Not>
- <Readonly />
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Normal Fields">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Not>
- <Or>
- <Static />
- <Readonly />
- </Or>
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Entry DisplayName="Internal Properties">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Kind Is="Property" />
- </And>
- </Entry.Match>
- </Entry>
- <Group DisplayName="Protected Fields">
- <Entry DisplayName="Constant Fields">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Or>
- <Kind Is="Constant" />
- <Readonly />
- <And>
- <Static />
- <Readonly />
- </And>
- </Or>
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Static Fields">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Static />
- <Not>
- <Readonly />
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Normal Fields">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Not>
- <Or>
- <Static />
- <Readonly />
- </Or>
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Entry DisplayName="Protected Properties">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Kind Is="Property" />
- </And>
- </Entry.Match>
- </Entry>
- <Group DisplayName="Private Fields">
- <Entry DisplayName="Constant Fields">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Or>
- <Kind Is="Constant" />
- <Readonly />
- <And>
- <Static />
- <Readonly />
- </And>
- </Or>
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Static Fields">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Static />
- <Not>
- <Readonly />
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Normal Fields">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Not>
- <Or>
- <Static />
- <Readonly />
- </Or>
- </Not>
- <Kind Is="Field" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Entry DisplayName="Private Properties">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Kind Is="Property" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Group DisplayName="Constructor/Destructor">
- <Entry DisplayName="Ctor">
- <Entry.Match>
- <Kind Is="Constructor" />
- </Entry.Match>
- </Entry>
- <Region Name="Disposal">
- <Entry DisplayName="Dtor">
- <Entry.Match>
- <Kind Is="Destructor" />
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Dispose()">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Kind Is="Method" />
- <Name Is="Dispose" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Dispose(true)">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Or>
- <Virtual />
- <Override />
- </Or>
- <Kind Is="Method" />
- <Name Is="Dispose" />
- </And>
- </Entry.Match>
- </Entry>
- </Region>
- </Group>
- <Group DisplayName="Methods">
- <Group DisplayName="Public">
- <Entry DisplayName="Static Methods">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Static />
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Methods">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Not>
- <Static />
- </Not>
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Group DisplayName="Internal">
- <Entry DisplayName="Static Methods">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Static />
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Methods">
- <Entry.Match>
- <And>
- <Access Is="Internal" />
- <Not>
- <Static />
- </Not>
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Group DisplayName="Protected">
- <Entry DisplayName="Static Methods">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Static />
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Methods">
- <Entry.Match>
- <And>
- <Access Is="Protected" />
- <Not>
- <Static />
- </Not>
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- <Group DisplayName="Private">
- <Entry DisplayName="Static Methods">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Static />
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- <Entry DisplayName="Methods">
- <Entry.Match>
- <And>
- <Access Is="Private" />
- <Not>
- <Static />
- </Not>
- <Kind Is="Method" />
- </And>
- </Entry.Match>
- </Entry>
- </Group>
- </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" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy>
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
- <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
- <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
- <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- o!f – Object Initializer: Anchor&Origin
- True
- constant("Centre")
- 0
- True
- True
- 2.0
- InCSharpFile
- ofao
- True
- Anchor = Anchor.$anchor$,
-Origin = Anchor.$anchor$,
- True
- True
- o!f – InternalChildren = []
- True
- True
- 2.0
- InCSharpFile
- ofic
- True
- InternalChildren = new Drawable[]
-{
- $END$
-};
- True
- True
- o!f – new GridContainer { .. }
- True
- True
- 2.0
- InCSharpFile
- ofgc
- True
- new GridContainer
-{
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[] { $END$ },
- new Drawable[] { }
- }
-};
- True
- True
- o!f – new FillFlowContainer { .. }
- True
- True
- 2.0
- InCSharpFile
- offf
- True
- new FillFlowContainer
-{
- RelativeSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- $END$
- }
-},
- True
- True
- o!f – new Container { .. }
- True
- True
- 2.0
- InCSharpFile
- ofcont
- True
- new Container
-{
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- $END$
- }
-},
- True
- True
- o!f – BackgroundDependencyLoader load()
- True
- True
- 2.0
- InCSharpFile
- ofbdl
- True
- [BackgroundDependencyLoader]
-private void load()
-{
- $END$
-}
- True
- True
- o!f – new Box { .. }
- True
- True
- 2.0
- InCSharpFile
- ofbox
- True
- new Box
-{
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
-},
- True
- True
- o!f – Children = []
- True
- True
- 2.0
- InCSharpFile
- ofc
- True
- Children = new Drawable[]
-{
- $END$
-};
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
diff --git a/osu.Android.slnf b/osu.Android.slnf
new file mode 100644
index 0000000000..7d90f97eb9
--- /dev/null
+++ b/osu.Android.slnf
@@ -0,0 +1,19 @@
+{
+ "solution": {
+ "path": "osu.sln",
+ "projects": [
+ "osu.Android\\osu.Android.csproj",
+ "osu.Game.Rulesets.Catch.Tests.Android\\osu.Game.Rulesets.Catch.Tests.Android.csproj",
+ "osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
+ "osu.Game.Rulesets.Mania.Tests.Android\\osu.Game.Rulesets.Mania.Tests.Android.csproj",
+ "osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj",
+ "osu.Game.Rulesets.Osu.Tests.Android\\osu.Game.Rulesets.Osu.Tests.Android.csproj",
+ "osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj",
+ "osu.Game.Rulesets.Taiko.Tests.Android\\osu.Game.Rulesets.Taiko.Tests.Android.csproj",
+ "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj",
+ "osu.Game.Tests.Android\\osu.Game.Tests.Android.csproj",
+ "osu.Game.Tests\\osu.Game.Tests.csproj",
+ "osu.Game\\osu.Game.csproj"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml
index acd21f9587..770eaf2222 100644
--- a/osu.Android/Properties/AndroidManifest.xml
+++ b/osu.Android/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
new file mode 100644
index 0000000000..e6b6446f72
--- /dev/null
+++ b/osu.Desktop.slnf
@@ -0,0 +1,20 @@
+{
+ "solution": {
+ "path": "osu.sln",
+ "projects": [
+ "osu.Desktop\\osu.Desktop.csproj",
+ "osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj",
+ "osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
+ "osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj",
+ "osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj",
+ "osu.Game.Rulesets.Osu.Tests\\osu.Game.Rulesets.Osu.Tests.csproj",
+ "osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj",
+ "osu.Game.Rulesets.Taiko.Tests\\osu.Game.Rulesets.Taiko.Tests.csproj",
+ "osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj",
+ "osu.Game.Tests\\osu.Game.Tests.csproj",
+ "osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
+ "osu.Game.Tournament\\osu.Game.Tournament.csproj",
+ "osu.Game\\osu.Game.csproj"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 7725ee6451..66e7bb381c 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -112,14 +112,14 @@ namespace osu.Desktop
{
protected override string LocateBasePath()
{
- bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
+ static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
string stableInstallPath;
try
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath))
return stableInstallPath;
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 2d1282634f..453cf6f94d 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,9 +1,7 @@
-
netcoreapp3.0
WinExe
- AnyCPU
true
click the circles. to the beat.
osu!
@@ -23,13 +21,13 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
index db95e18f13..0fa3b7730d 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
index 7990c35e09..be6044bbd0 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -1,6 +1,5 @@
-
+
-
Debug
iPhoneSimulator
@@ -33,5 +32,4 @@
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 720ef1db42..9b529a2e4c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -11,12 +11,12 @@ using System;
using System.Collections.Generic;
using osu.Game.Skinning;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue
},
- new SpriteText
+ new OsuSpriteText
{
Text = "custom"
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 7b8c699f2c..da36673930 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -40,8 +40,10 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
for (int i = 0; i < 512; i++)
+ {
if (i % 5 < 3)
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
+ }
return beatmap;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 4b629902cb..1dbe9b39ee 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 0d9a663b9f..b5497ea89f 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -8,6 +8,7 @@ using System;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
+using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -22,48 +23,44 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
- var curveData = obj as IHasCurve;
var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo;
- var endTime = obj as IHasEndTime;
- var legacyOffset = obj as IHasLegacyLastTickOffset;
- if (curveData != null)
+ switch (obj)
{
- yield return new JuiceStream
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- Path = curveData.Path,
- NodeSamples = curveData.NodeSamples,
- RepeatCount = curveData.RepeatCount,
- X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
- NewCombo = comboData?.NewCombo ?? false,
- ComboOffset = comboData?.ComboOffset ?? 0,
- LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
- };
- }
- else if (endTime != null)
- {
- yield return new BananaShower
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- Duration = endTime.Duration,
- NewCombo = comboData?.NewCombo ?? false,
- ComboOffset = comboData?.ComboOffset ?? 0,
- };
- }
- else
- {
- yield return new Fruit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- NewCombo = comboData?.NewCombo ?? false,
- ComboOffset = comboData?.ComboOffset ?? 0,
- X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
- };
+ case IHasCurve curveData:
+ return new JuiceStream
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ Path = curveData.Path,
+ NodeSamples = curveData.NodeSamples,
+ RepeatCount = curveData.RepeatCount,
+ X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
+ NewCombo = comboData?.NewCombo ?? false,
+ ComboOffset = comboData?.ComboOffset ?? 0,
+ LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
+ }.Yield();
+
+ case IHasEndTime endTime:
+ return new BananaShower
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ Duration = endTime.Duration,
+ NewCombo = comboData?.NewCombo ?? false,
+ ComboOffset = comboData?.ComboOffset ?? 0,
+ }.Yield();
+
+ default:
+ return new Fruit
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ NewCombo = comboData?.NewCombo ?? false,
+ ComboOffset = comboData?.ComboOffset ?? 0,
+ X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
+ }.Yield();
}
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 5ab47c1611..db52fbac1b 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -8,7 +8,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
-using osuTK;
using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Mods;
@@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
catchObject.XOffset = 0;
if (catchObject is TinyDroplet)
- catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
+ catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
}
@@ -195,10 +194,15 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
if (currentObject is Fruit)
objectWithDroplets.Add(currentObject);
+
if (currentObject is JuiceStream)
+ {
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
+ {
if (!(currentJuiceElement is TinyDroplet))
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
+ }
+ }
}
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
@@ -225,7 +229,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
else
{
currentObject.DistanceToHyperDash = distanceToHyper;
- lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
+ lastExcess = Math.Clamp(distanceToHyper, 0, halfCatcherWidth);
}
lastDirection = thisDirection;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 5a640f6d1a..3c237c86be 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@@ -48,55 +47,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return 0;
// We are heavily relying on aim in catch the beat
- double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f;
+ double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
int numTotalHits = totalComboHits();
// Longer maps are worth more
- float lengthBonus =
- 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) +
- (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f);
+ double lengthBonus =
+ 0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) +
+ (numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0);
// Longer maps are worth more
value *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
- value *= Math.Pow(0.97f, misses);
+ value *= Math.Pow(0.97, misses);
// Combo scaling
- float beatmapMaxCombo = Attributes.MaxCombo;
- if (beatmapMaxCombo > 0)
- value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+ if (Attributes.MaxCombo > 0)
+ value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
- float approachRate = (float)Attributes.ApproachRate;
- float approachRateFactor = 1.0f;
- if (approachRate > 9.0f)
- approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
- else if (approachRate < 8.0f)
- approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
+ double approachRateFactor = 1.0;
+ if (Attributes.ApproachRate > 9.0)
+ approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9
+ else if (Attributes.ApproachRate < 8.0)
+ approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8
value *= approachRateFactor;
if (mods.Any(m => m is ModHidden))
// Hiddens gives nothing on max approach rate, and more the lower it is
- value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10
+ value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
if (mods.Any(m => m is ModFlashlight))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
- value *= 1.35f * lengthBonus;
+ value *= 1.35 * lengthBonus;
// Scale the aim value with accuracy _slightly_
- value *= Math.Pow(accuracy(), 5.5f);
+ value *= Math.Pow(accuracy(), 5.5);
// Custom multipliers for NoFail. SpunOut is not applicable.
if (mods.Any(m => m is ModNoFail))
- value *= 0.90f;
+ value *= 0.90;
return value;
}
- private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f);
+ private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
private int totalComboHits() => misses + ticksHit + fruitsHit;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index d146153294..7cd569035b 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
@@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
if (lastPlayerPosition == null)
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
- float playerPosition = MathHelper.Clamp(
+ float playerPosition = Math.Clamp(
lastPlayerPosition.Value,
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 6d44e4660e..267e6d12c7 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -27,11 +27,13 @@ namespace osu.Game.Rulesets.Catch.Objects
return;
for (double i = StartTime; i <= EndTime; i += spacing)
+ {
AddNested(new Banana
{
Samples = Samples,
StartTime = i
});
+ }
}
public double EndTime => StartTime + Duration;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index dd4a58a5ef..b7c05392f3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -4,8 +4,8 @@
using System;
using osuTK;
using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
protected override void UpdateStateTransforms(ArmedState state)
{
- var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
+ var endTime = HitObject.GetEndTime();
using (BeginAbsoluteSequence(endTime, true))
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 1af77b75fc..958cd19d50 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
const float small_pulp = large_pulp_3 / 2;
- Vector2 positionAt(float angle, float distance) => new Vector2(
- distance * (float)Math.Sin(angle * Math.PI / 180),
- distance * (float)Math.Cos(angle * Math.PI / 180));
+ static Vector2 positionAt(float angle, float distance) => new Vector2(
+ distance * MathF.Sin(angle * MathF.PI / 180),
+ distance * MathF.Cos(angle * MathF.PI / 180));
switch (representation)
{
@@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
base.Update();
- border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
+ border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
}
private Color4 colourForRepresentation(FruitVisualRepresentation representation)
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 0952e8981a..33780427b6 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -116,17 +116,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Duration => EndTime - StartTime;
- private SliderPath path;
-
- public SliderPath Path
- {
- get => path;
- set => path = value;
- }
+ public SliderPath Path { get; set; }
public double Distance => Path.Distance;
- public List> NodeSamples { get; set; } = new List>();
+ public List> NodeSamples { get; set; } = new List>();
public double? LegacyLastTickOffset { get; set; }
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 56c8b33e02..49393a836f 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Catch.UI
var additive = createCatcherSprite();
additive.Anchor = Anchor;
- additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
+ additive.OriginPosition += new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
additive.Position = Position;
additive.Scale = Scale;
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
@@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Y -= RNG.NextSingle() * diff;
}
- fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
+ fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
caughtFruit.Add(fruit);
}
@@ -378,7 +378,7 @@ namespace osu.Game.Rulesets.Catch.UI
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
- X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
+ X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
// Correct overshooting.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index 883cac67d1..b19affbf9f 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -1,9 +1,7 @@
-
- netstandard2.0
+ netstandard2.1
Library
- AnyCPU
true
catch the fruit. to the beat.
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
index e6728c801d..de7935b2ef 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
index 58c2e2aa5a..88ad484bc1 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
@@ -1,6 +1,5 @@
-
+
-
Debug
iPhoneSimulator
@@ -33,5 +32,4 @@
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 6f10540973..12865385b6 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -9,7 +9,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
@@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests
yield return new ConvertValue
{
StartTime = hitObject.StartTime,
- EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+ EndTime = hitObject.GetEndTime(),
Column = ((ManiaHitObject)hitObject).Column
};
}
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 30511d672d..8fc4dbfe72 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index e10602312e..9069c09ae4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
if (TargetColumns >= 10)
{
- TargetColumns = TargetColumns / 2;
+ TargetColumns /= 2;
Dual = true;
}
}
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
- int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
+ int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
Random = new FastRandom(seed);
return base.ConvertBeatmap(original);
@@ -156,37 +156,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The hit objects generated.
private IEnumerable generateConverted(HitObject original, IBeatmap originalBeatmap)
{
- var endTimeData = original as IHasEndTime;
- var distanceData = original as IHasDistance;
- var positionData = original as IHasPosition;
-
Patterns.PatternGenerator conversion = null;
- if (distanceData != null)
+ switch (original)
{
- var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
- conversion = generator;
-
- for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
+ case IHasDistance _:
{
- recordNote(time, positionData?.Position ?? Vector2.Zero);
- computeDensity(time);
+ var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
+ conversion = generator;
+
+ var positionData = original as IHasPosition;
+
+ for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
+ {
+ recordNote(time, positionData?.Position ?? Vector2.Zero);
+ computeDensity(time);
+ }
+
+ break;
}
- }
- else if (endTimeData != null)
- {
- conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
- recordNote(endTimeData.EndTime, new Vector2(256, 192));
- computeDensity(endTimeData.EndTime);
- }
- else if (positionData != null)
- {
- computeDensity(original.StartTime);
+ case IHasEndTime endTimeData:
+ {
+ conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
- conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
+ recordNote(endTimeData.EndTime, new Vector2(256, 192));
+ computeDensity(endTimeData.EndTime);
+ break;
+ }
- recordNote(original.StartTime, positionData.Position);
+ case IHasPosition positionData:
+ {
+ computeDensity(original.StartTime);
+
+ conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
+
+ recordNote(original.StartTime, positionData.Position);
+ break;
+ }
}
if (conversion == null)
@@ -219,14 +226,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private Pattern generate()
{
- var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition;
int column = GetColumn(positionData?.X ?? 0);
var pattern = new Pattern();
- if (endTimeData != null)
+ if (HitObject is IHasEndTime endTimeData)
{
pattern.Add(new HoldNote
{
@@ -237,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
});
}
- else if (positionData != null)
+ else if (HitObject is IHasXPosition)
{
pattern.Add(new Note
{
@@ -255,11 +261,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
/// The time to retrieve the sample info list from.
///
- private List sampleInfoListAt(double time)
+ private IList sampleInfoListAt(double time)
{
- var curveData = HitObject as IHasCurve;
-
- if (curveData == null)
+ if (!(HitObject is IHasCurve curveData))
return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index ea418eedb4..9565ac8994 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
foreach (var obj in originalPattern.HitObjects)
{
- if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
+ if (!Precision.AlmostEquals(EndTime, obj.GetEndTime()))
intermediatePattern.Add(obj);
else
endTimePattern.Add(obj);
@@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
- bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
+ static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
@@ -472,11 +472,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// The time to retrieve the sample info list from.
///
- private List sampleInfoListAt(double time)
+ private IList sampleInfoListAt(double time)
{
- var curveData = HitObject as IHasCurve;
-
- if (curveData == null)
+ if (!(HitObject is IHasCurve curveData))
return HitObject.Samples;
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index decd159ee9..84f950997d 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@@ -88,15 +89,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public override IEnumerable Generate()
{
- yield return generate();
- }
-
- private Pattern generate()
- {
- var pattern = new Pattern();
-
- try
+ Pattern generateCore()
{
+ var pattern = new Pattern();
+
if (TotalColumns == 1)
{
addToPattern(pattern, 0);
@@ -109,8 +105,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
// Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++)
+ {
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
+ }
return pattern;
}
@@ -132,8 +130,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
// Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++)
+ {
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
+ }
return pattern;
}
@@ -164,54 +164,56 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
if (convertType.HasFlag(PatternType.KeepSingle))
- return pattern = generateRandomNotes(1);
+ return generateRandomNotes(1);
if (convertType.HasFlag(PatternType.Mirror))
{
if (ConversionDifficulty > 6.5)
- return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
+ return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
- return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
+ return generateRandomPatternWithMirrored(0.12, 0.17, 0);
- return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
+ return generateRandomPatternWithMirrored(0.12, 0, 0);
}
if (ConversionDifficulty > 6.5)
{
if (convertType.HasFlag(PatternType.LowProbability))
- return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
+ return generateRandomPattern(0.78, 0.42, 0, 0);
- return pattern = generateRandomPattern(1, 0.62, 0, 0);
+ return generateRandomPattern(1, 0.62, 0, 0);
}
if (ConversionDifficulty > 4)
{
if (convertType.HasFlag(PatternType.LowProbability))
- return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
+ return generateRandomPattern(0.35, 0.08, 0, 0);
- return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
+ return generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 2)
{
if (convertType.HasFlag(PatternType.LowProbability))
- return pattern = generateRandomPattern(0.18, 0, 0, 0);
+ return generateRandomPattern(0.18, 0, 0, 0);
- return pattern = generateRandomPattern(0.45, 0, 0, 0);
+ return generateRandomPattern(0.45, 0, 0, 0);
}
- return pattern = generateRandomPattern(0, 0, 0, 0);
+ return generateRandomPattern(0, 0, 0, 0);
}
- finally
+
+ var p = generateCore();
+
+ foreach (var obj in p.HitObjects)
{
- foreach (var obj in pattern.HitObjects)
- {
- if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
- StairType = PatternType.ReverseStair;
- if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
- StairType = PatternType.Stair;
- }
+ if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
+ StairType = PatternType.ReverseStair;
+ if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
+ StairType = PatternType.Stair;
}
+
+ return p.Yield();
}
///
@@ -299,8 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
- bool addToCentre;
- int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
+ int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out var addToCentre);
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
int nextColumn = GetRandomColumn(upperBound: columnLimit);
@@ -380,8 +381,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
{
- addToCentre = false;
-
switch (TotalColumns)
{
case 2:
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index fba52dfc32..fb58d805a9 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -7,7 +7,6 @@ using JetBrains.Annotations;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
-using osuTK;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@@ -54,11 +53,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (allowSpecial && TotalColumns == 8)
{
const float local_x_divisor = 512f / 7;
- return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1;
+ return Math.Clamp((int)MathF.Floor(position / local_x_divisor), 0, 6) + 1;
}
float localXDivisor = 512f / TotalColumns;
- return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1);
+ return Math.Clamp((int)MathF.Floor(position / localXDivisor), 0, TotalColumns - 1);
}
///
@@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
+ conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
@@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// A function to retrieve the next column. If null, a randomisation scheme will be used.
/// A function to perform additional validation checks to determine if a column is a valid candidate for a .
/// The minimum column index. If null, is used.
- /// The maximum column index. If null, is used.
+ /// The maximum column index. If null, TotalColumns is used.
/// A list of patterns for which the validity of a column should be checked against.
/// A column is not a valid candidate if a occupies the same column in any of the patterns.
/// A column which has passed the check and for which there are no
@@ -148,9 +147,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func nextColumn = null, [InstantHandle] Func validation = null,
params Pattern[] patterns)
{
- lowerBound = lowerBound ?? RandomStart;
- upperBound = upperBound ?? TotalColumns;
- nextColumn = nextColumn ?? (_ => GetRandomColumn(lowerBound, upperBound));
+ lowerBound ??= RandomStart;
+ upperBound ??= TotalColumns;
+ nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound));
// Check for the initial column
if (isValid(initialColumn))
@@ -184,7 +183,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// Returns a random column index in the range [, ).
///
/// The minimum column index. If null, is used.
- /// The maximum column index. If null, is used.
+ /// The maximum column index. If null, is used.
protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
///
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
new file mode 100644
index 0000000000..acce41db6f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Mania.Edit.Blueprints
+{
+ public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint
+ {
+ protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
+
+ private readonly HoldNotePosition position;
+
+ public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position)
+ : base(holdNote)
+ {
+ this.position = position;
+ InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
+
+ Select();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
+ if (DrawableObject.IsLoaded)
+ {
+ DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail;
+
+ Anchor = note.Anchor;
+ Origin = note.Origin;
+
+ Size = note.DrawSize;
+ Position = note.DrawPosition;
+ }
+ }
+
+ // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
+ public override bool HandlePositionalInput => false;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs
new file mode 100644
index 0000000000..219dad566d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania.Edit.Blueprints
+{
+ public enum HoldNotePosition
+ {
+ Start,
+ End
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 3a9eb1f043..56c0b671a0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
InternalChildren = new Drawable[]
{
- new HoldNoteNoteSelectionBlueprint(DrawableObject.Head),
- new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail),
+ new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
+ new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
new BodyPiece
{
AccentColour = Color4.Transparent,
@@ -54,37 +54,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
- Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
+ // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
+ if (DrawableObject.IsLoaded)
+ {
+ Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
- // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
- // When scrolling upwards our origin is already at the top of the head note (which is the intended location),
- // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
- if (direction.Value == ScrollingDirection.Down)
- Y -= DrawableObject.Tail.DrawHeight;
+ // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
+ // When scrolling upwards our origin is already at the top of the head note (which is the intended location),
+ // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
+ if (direction.Value == ScrollingDirection.Down)
+ Y -= DrawableObject.Tail.DrawHeight;
+ }
}
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
-
- private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint
- {
- public HoldNoteNoteSelectionBlueprint(DrawableNote note)
- : base(note)
- {
- Select();
- }
-
- protected override void Update()
- {
- base.Update();
-
- Anchor = DrawableObject.Anchor;
- Origin = DrawableObject.Origin;
-
- Position = DrawableObject.DrawPosition;
- }
-
- // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
- public override bool HandlePositionalInput => false;
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
index b83c4aa9aa..2bff33c4cf 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
@@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
- Size = DrawableObject.DrawSize;
+ // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
+ if (DrawableObject.IsLoaded)
+ Size = DrawableObject.DrawSize;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 732231b0d9..618af3e772 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Timing;
@@ -9,7 +10,6 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
-using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit
editorClock = clock;
}
- public override void HandleMovement(MoveSelectionEvent moveEvent)
+ public override bool HandleMovement(MoveSelectionEvent moveEvent)
{
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Edit
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent);
- base.HandleMovement(moveEvent);
+ return true;
}
///
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit
// Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
- targetPosition = targetPosition - referenceParent.DrawHeight;
+ targetPosition -= referenceParent.DrawHeight;
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Edit
maxColumn = obj.Column;
}
- columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
+ columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
foreach (var obj in SelectedHitObjects.OfType())
obj.Column += columnDelta;
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 2b336ca16d..483327d5b3 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -6,7 +6,6 @@ using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
@@ -84,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Replays
var currentObject = Beatmap.HitObjects[i];
var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
- double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime;
+ double endTime = currentObject.GetEndTime();
bool canDelayKeyUp = nextObjectInColumn == null ||
nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 5ab07416a6..08f6049782 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI
foreach (var stage in stages)
{
- sum = sum + stage.Columns.Count;
+ sum += stage.Columns.Count;
if (sum > column)
return stage;
}
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index a086da0565..07ef1022ae 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -1,9 +1,7 @@
-
- netstandard2.0
+ netstandard2.1
Library
- AnyCPU
true
smash the keys. to the beat.
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
index aad907b241..3ce17ccc27 100644
--- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
index c7787bd162..545abcec6c 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
@@ -1,6 +1,5 @@
-
+
-
Debug
iPhoneSimulator
@@ -33,5 +32,4 @@
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index e9fdf924c3..450f7de6d2 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
@@ -41,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Tests
break;
}
- ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
+ static ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
- EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
+ EndTime = obj.GetEndTime(),
X = obj.StackedPosition.X,
Y = obj.StackedPosition.Y
};
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
index 685a51d208..46769f65fe 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -101,7 +101,11 @@ namespace osu.Game.Rulesets.Osu.Tests
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
- public event Action SourceChanged;
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
}
private class MovingCursorInputManager : ManualInputManager
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index 433ec6bd25..ac627aa23e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -24,12 +24,14 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneDrawableJudgement()
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ {
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}));
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
new file mode 100644
index 0000000000..94ca2d4cd1
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
@@ -0,0 +1,230 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneFollowPoints : OsuTestScene
+ {
+ private Container hitObjectContainer;
+ private FollowPointRenderer followPointRenderer;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Children = new Drawable[]
+ {
+ hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both },
+ followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }
+ };
+ });
+
+ [Test]
+ public void TestAddObject()
+ {
+ addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestRemoveObject()
+ {
+ addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
+
+ removeObjectStep(() => getObject(0));
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestAddMultipleObjects()
+ {
+ addMultipleObjectsStep();
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestRemoveEndObject()
+ {
+ addMultipleObjectsStep();
+
+ removeObjectStep(() => getObject(4));
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestRemoveStartObject()
+ {
+ addMultipleObjectsStep();
+
+ removeObjectStep(() => getObject(0));
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestRemoveMiddleObject()
+ {
+ addMultipleObjectsStep();
+
+ removeObjectStep(() => getObject(2));
+
+ assertGroups();
+ }
+
+ [Test]
+ public void TestMoveObject()
+ {
+ addMultipleObjectsStep();
+
+ AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
+
+ assertGroups();
+ }
+
+ [TestCase(0, 0)] // Start -> Start
+ [TestCase(0, 2)] // Start -> Middle
+ [TestCase(0, 5)] // Start -> End
+ [TestCase(2, 0)] // Middle -> Start
+ [TestCase(1, 3)] // Middle -> Middle (forwards)
+ [TestCase(3, 1)] // Middle -> Middle (backwards)
+ [TestCase(4, 0)] // End -> Start
+ [TestCase(4, 2)] // End -> Middle
+ [TestCase(4, 4)] // End -> End
+ public void TestReorderObjects(int startIndex, int endIndex)
+ {
+ addMultipleObjectsStep();
+
+ reorderObjectStep(startIndex, endIndex);
+
+ assertGroups();
+ }
+
+ private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[]
+ {
+ new HitCircle { Position = new Vector2(100, 100) },
+ new HitCircle { Position = new Vector2(200, 200) },
+ new HitCircle { Position = new Vector2(300, 300) },
+ new HitCircle { Position = new Vector2(400, 400) },
+ new HitCircle { Position = new Vector2(500, 500) },
+ });
+
+ private void addObjectsStep(Func ctorFunc)
+ {
+ AddStep("add hitobjects", () =>
+ {
+ var objects = ctorFunc();
+
+ for (int i = 0; i < objects.Length; i++)
+ {
+ objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1);
+ objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ DrawableOsuHitObject drawableObject = null;
+
+ switch (objects[i])
+ {
+ case HitCircle circle:
+ drawableObject = new DrawableHitCircle(circle);
+ break;
+
+ case Slider slider:
+ drawableObject = new DrawableSlider(slider);
+ break;
+
+ case Spinner spinner:
+ drawableObject = new DrawableSpinner(spinner);
+ break;
+ }
+
+ hitObjectContainer.Add(drawableObject);
+ followPointRenderer.AddFollowPoints(drawableObject);
+ }
+ });
+ }
+
+ private void removeObjectStep(Func getFunc)
+ {
+ AddStep("remove hitobject", () =>
+ {
+ var drawableObject = getFunc?.Invoke();
+
+ hitObjectContainer.Remove(drawableObject);
+ followPointRenderer.RemoveFollowPoints(drawableObject);
+ });
+ }
+
+ private void reorderObjectStep(int startIndex, int endIndex)
+ {
+ AddStep($"move object {startIndex} to {endIndex}", () =>
+ {
+ DrawableOsuHitObject toReorder = getObject(startIndex);
+
+ double targetTime;
+ if (endIndex < hitObjectContainer.Count)
+ targetTime = getObject(endIndex).HitObject.StartTime - 1;
+ else
+ targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1;
+
+ hitObjectContainer.Remove(toReorder);
+ toReorder.HitObject.StartTime = targetTime;
+ hitObjectContainer.Add(toReorder);
+ });
+ }
+
+ private void assertGroups()
+ {
+ AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
+ AddAssert("group endpoints are correct", () =>
+ {
+ for (int i = 0; i < hitObjectContainer.Count; i++)
+ {
+ DrawableOsuHitObject expectedStart = getObject(i);
+ DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
+
+ if (getGroup(i).Start != expectedStart)
+ throw new AssertionException($"Object {i} expected to be the start of group {i}.");
+
+ if (getGroup(i).End != expectedEnd)
+ throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
+ }
+
+ return true;
+ });
+ }
+
+ private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
+
+ private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
+
+ private class TestHitObjectContainer : Container
+ {
+ protected override int Compare(Drawable x, Drawable y)
+ {
+ var osuX = (DrawableOsuHitObject)x;
+ var osuY = (DrawableOsuHitObject)y;
+
+ int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime);
+
+ if (compare == 0)
+ return base.Compare(x, y);
+
+ return compare;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 84a7bfc53e..64f353c4d9 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{
- positionOffset = positionOffset ?? Vector2.Zero;
+ positionOffset ??= Vector2.Zero;
var circle = new HitCircle
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index 95c2810e94..b99cd523ff 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -29,8 +29,10 @@ namespace osu.Game.Rulesets.Osu.Tests
};
for (int i = 0; i < 512; i++)
+ {
if (i % 32 < 20)
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
+ }
return beatmap;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index a9a6097182..eff4d919b0 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -42,11 +42,19 @@ namespace osu.Game.Rulesets.Osu.Tests
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
- private readonly TestOsuDistanceSnapGrid grid;
+ private TestOsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ }
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
+ editorBeatmap.ControlPointInfo.Clear();
+ editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
Children = new Drawable[]
{
@@ -58,14 +66,6 @@ namespace osu.Game.Rulesets.Osu.Tests
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
- }
-
- [SetUp]
- public void Setup() => Schedule(() =>
- {
- editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
- editorBeatmap.ControlPointInfo.Clear();
- editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
});
[TestCase(1)]
@@ -102,6 +102,27 @@ namespace osu.Game.Rulesets.Osu.Tests
assertSnappedDistance((float)beat_length * 2);
}
+ [Test]
+ public void TestLimitedDistance()
+ {
+ AddStep("create limited grid", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
+ new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
+ };
+ });
+
+ AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f)));
+ assertSnappedDistance((float)beat_length * 2);
+ }
+
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
{
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
@@ -152,8 +173,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public new float DistanceSpacing => base.DistanceSpacing;
- public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
- : base(hitObject)
+ public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null)
+ : base(hitObject, nextHitObject)
{
}
}
@@ -164,9 +185,9 @@ namespace osu.Game.Rulesets.Osu.Tests
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
- public float DurationToDistance(double referenceTime, double duration) => 0;
+ public float DurationToDistance(double referenceTime, double duration) => (float)duration;
- public double DistanceToDuration(double referenceTime, float distance) => 0;
+ public double DistanceToDuration(double referenceTime, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index 02c65db6ad..4da1b1dae0 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -16,9 +16,11 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
+using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
@@ -75,14 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin);
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin);
public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{
private readonly ISkinSource skin;
- public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
- : base(beatmap, frameBasedClock, audio)
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
+ : base(beatmap, storyboard, frameBasedClock, audio)
{
this.skin = skin;
}
@@ -124,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (!enabled) return null;
- return new SpriteText
+ return new OsuSpriteText
{
Text = identifier,
Font = OsuFont.Default.With(size: 30),
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index a955911bd5..a9d5c03517 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -126,6 +126,68 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
}
+ [Test]
+ public void TestChangeSamplesWithNoNodeSamples()
+ {
+ DrawableSlider slider = null;
+
+ AddStep("create slider", () =>
+ {
+ slider = (DrawableSlider)createSlider(repeats: 1);
+ Add(slider);
+ });
+
+ AddStep("change samples", () => slider.HitObject.Samples = new[]
+ {
+ new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
+ new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
+ });
+
+ AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
+ AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
+ AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
+
+ static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
+
+ static bool assertSamples(HitObject hitObject)
+ {
+ return hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)
+ && hitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE);
+ }
+ }
+
+ [Test]
+ public void TestChangeSamplesWithNodeSamples()
+ {
+ DrawableSlider slider = null;
+
+ AddStep("create slider", () =>
+ {
+ slider = (DrawableSlider)createSlider(repeats: 1);
+
+ for (int i = 0; i < 2; i++)
+ ((Slider)slider.HitObject).NodeSamples.Add(new List { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
+
+ Add(slider);
+ });
+
+ AddStep("change samples", () => slider.HitObject.Samples = new[]
+ {
+ new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
+ new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
+ });
+
+ AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
+ AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
+ AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
+
+ static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
+
+ static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
+ }
+
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
@@ -143,7 +205,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(52, -34)
}, 700),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats),
StackHeight = 10
};
@@ -174,7 +235,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(distance, 0),
}, distance),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats),
StackHeight = stackHeight
};
@@ -194,7 +254,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(400, 0)
}, 600),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@@ -218,7 +277,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(430, 0)
}),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@@ -241,7 +299,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(430, 0)
}),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@@ -265,7 +322,6 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(0, -200)
}),
RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
};
return createDrawable(slider, 2, 3);
@@ -275,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable createCatmull(int repeats = 0)
{
- var repeatSamples = new List>();
+ var repeatSamples = new List>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List());
@@ -297,14 +353,6 @@ namespace osu.Game.Rulesets.Osu.Tests
return createDrawable(slider, 3, 1);
}
- private List> createEmptySamples(int repeats)
- {
- var repeatSamples = new List>();
- for (int i = 0; i < repeats; i++)
- repeatSamples.Add(new List());
- return repeatSamples;
- }
-
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{
var cpi = new ControlPointInfo();
@@ -332,8 +380,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
- var osuObject = judgedObject as DrawableOsuHitObject;
- if (osuObject == null)
+ if (!(judgedObject is DrawableOsuHitObject osuObject))
return;
OsuSpriteText text;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index cded7f0e95..d0ce0c33c2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -15,6 +15,7 @@ using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System.Linq;
+using osu.Game.Storyboards;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
@@ -28,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override bool Autoplay => true;
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
- var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = (TrackVirtualManual)working.Track;
return working;
}
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 3aea9e0387..fddf176fd0 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 6a41e93c35..2296030f81 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
using System;
using osu.Game.Rulesets.Osu.UI;
+using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Osu.Beatmaps
{
@@ -23,52 +24,48 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
- var curveData = original as IHasCurve;
- var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition;
var comboData = original as IHasCombo;
- var legacyOffset = original as IHasLegacyLastTickOffset;
- if (curveData != null)
+ switch (original)
{
- yield return new Slider
- {
- StartTime = original.StartTime,
- Samples = original.Samples,
- Path = curveData.Path,
- NodeSamples = curveData.NodeSamples,
- RepeatCount = curveData.RepeatCount,
- Position = positionData?.Position ?? Vector2.Zero,
- NewCombo = comboData?.NewCombo ?? false,
- ComboOffset = comboData?.ComboOffset ?? 0,
- LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset,
- // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
- // this results in more (or less) ticks being generated in stackThreshold)
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectN = beatmap.HitObjects[n];
if (objectN is Spinner) continue;
- double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime;
+ double endTime = objectN.GetEndTime();
if (objectI.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the previous object.
@@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
continue;
- double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime;
+ double startTime = currHitObject.GetEndTime();
int sliderStack = 0;
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
@@ -217,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{
currHitObject.StackHeight++;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
+ startTime = beatmap.HitObjects[j].GetEndTime();
}
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
{
//Case for sliders - bump notes down and right, rather than up and left.
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
+ startTime = beatmap.HitObjects[j].GetEndTime();
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 093081b6a1..05c78cbc95 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -55,22 +55,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return 0;
// Custom multipliers for NoFail and SpunOut.
- double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+ double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (mods.Any(m => m is OsuModNoFail))
- multiplier *= 0.90f;
+ multiplier *= 0.90;
if (mods.Any(m => m is OsuModSpunOut))
- multiplier *= 0.95f;
+ multiplier *= 0.95;
double aimValue = computeAimValue();
double speedValue = computeSpeedValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
- Math.Pow(aimValue, 1.1f) +
- Math.Pow(speedValue, 1.1f) +
- Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
+ Math.Pow(aimValue, 1.1) +
+ Math.Pow(speedValue, 1.1) +
+ Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryRatings != null)
@@ -93,82 +93,82 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModTouchDevice))
rawAim = Math.Pow(rawAim, 0.8);
- double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+ double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more
- double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
- (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
- aimValue *= Math.Pow(0.97f, countMiss);
+ aimValue *= Math.Pow(0.97, countMiss);
// Combo scaling
if (beatmapMaxCombo > 0)
- aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+ aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
- double approachRateFactor = 1.0f;
+ double approachRateFactor = 1.0;
- if (Attributes.ApproachRate > 10.33f)
- approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
- else if (Attributes.ApproachRate < 8.0f)
+ if (Attributes.ApproachRate > 10.33)
+ approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
+ else if (Attributes.ApproachRate < 8.0)
{
- approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
+ approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
- aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
+ aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
if (mods.Any(h => h is OsuModFlashlight))
{
// Apply object-based bonus for flashlight.
- aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
+ aimValue *= 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) +
(totalHits > 200
- ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
- (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f)
- : 0.0f);
+ ? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) +
+ (totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0)
+ : 0.0);
}
// Scale the aim value with accuracy _slightly_
- aimValue *= 0.5f + accuracy / 2.0f;
+ aimValue *= 0.5 + accuracy / 2.0;
// It is important to also consider accuracy difficulty when doing that
- aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
+ aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
}
private double computeSpeedValue()
{
- double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+ double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more
- speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
- (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+ speedValue *= 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
- speedValue *= Math.Pow(0.97f, countMiss);
+ speedValue *= Math.Pow(0.97, countMiss);
// Combo scaling
if (beatmapMaxCombo > 0)
- speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+ speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(beatmapMaxCombo, 0.8), 1.0);
- double approachRateFactor = 1.0f;
- if (Attributes.ApproachRate > 10.33f)
- approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
+ double approachRateFactor = 1.0;
+ if (Attributes.ApproachRate > 10.33)
+ approachRateFactor += 0.3 * (Attributes.ApproachRate - 10.33);
speedValue *= approachRateFactor;
if (mods.Any(m => m is OsuModHidden))
- speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate);
+ speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
// Scale the speed value with accuracy _slightly_
- speedValue *= 0.02f + accuracy;
+ speedValue *= 0.02 + accuracy;
// It is important to also consider accuracy difficulty when doing that
- speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
+ speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
return speedValue;
}
@@ -190,15 +190,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+ double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
- accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
+ accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
if (mods.Any(m => m is OsuModHidden))
- accuracyValue *= 1.08f;
+ accuracyValue *= 1.08;
if (mods.Any(m => m is OsuModFlashlight))
- accuracyValue *= 1.02f;
+ accuracyValue *= 1.02;
return accuracyValue;
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index eacac7ae6a..fa6c5c4d9c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
if (progress % 2 >= 1)
progress = 1 - progress % 1;
else
- progress = progress % 1;
+ progress %= 1;
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
index 2b6b93a590..2868ddeaa4 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
@@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
CornerRadius = Size.X / 2;
+ CornerExponent = 2;
InternalChild = new RingPiece();
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index e2ea6a12d7..0ccf020300 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -10,15 +10,17 @@ using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointPiece : BlueprintPiece
{
- public Action RequestSelection;
+ public Action RequestSelection;
public Action ControlPointsChanged;
public readonly BindableBool IsSelected = new BindableBool();
@@ -29,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private readonly Container marker;
private readonly Drawable markerRing;
- private bool isClicked;
+ [Resolved(CanBeNull = true)]
+ private IDistanceSnapProvider snapProvider { get; set; }
[Resolved]
private OsuColour colours { get; set; }
@@ -101,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
markerRing.Alpha = IsSelected.Value ? 1 : 0;
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
- if (IsHovered || isClicked || IsSelected.Value)
+ if (IsHovered || IsSelected.Value)
colour = Color4.White;
marker.Colour = colour;
}
@@ -127,23 +130,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnMouseDown(MouseDownEvent e)
{
- isClicked = true;
- return true;
+ if (RequestSelection == null)
+ return false;
+
+ switch (e.Button)
+ {
+ case MouseButton.Left:
+ RequestSelection.Invoke(Index, e);
+ return true;
+
+ case MouseButton.Right:
+ if (!IsSelected.Value)
+ RequestSelection.Invoke(Index, e);
+ return false; // Allow context menu to show
+ }
+
+ return false;
}
- protected override bool OnMouseUp(MouseUpEvent e)
- {
- isClicked = false;
- return true;
- }
+ protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
- protected override bool OnClick(ClickEvent e)
- {
- RequestSelection?.Invoke(Index);
- return true;
- }
+ protected override bool OnClick(ClickEvent e) => RequestSelection != null;
- protected override bool OnDragStart(DragStartEvent e) => true;
+ protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left;
protected override bool OnDrag(DragEvent e)
{
@@ -151,12 +160,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (Index == 0)
{
- // Special handling for the head - only the position of the slider changes
- slider.Position += e.Delta;
+ // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
+ (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
+ Vector2 movementDelta = snappedPosition - slider.Position;
+
+ slider.Position += movementDelta;
+ slider.StartTime = snappedTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
for (int i = 1; i < newControlPoints.Length; i++)
- newControlPoints[i] -= e.Delta;
+ newControlPoints[i] -= movementDelta;
}
else
newControlPoints[Index] += e.Delta;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index b70c11427a..cdca48490e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -2,27 +2,42 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Humanizer;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit.Compose;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
- public class PathControlPointVisualiser : CompositeDrawable
+ public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
{
public Action ControlPointsChanged;
internal readonly Container Pieces;
private readonly Slider slider;
+ private readonly bool allowSelection;
private InputManager inputManager;
- public PathControlPointVisualiser(Slider slider)
+ [Resolved(CanBeNull = true)]
+ private IPlacementHandler placementHandler { get; set; }
+
+ public PathControlPointVisualiser(Slider slider, bool allowSelection)
{
this.slider = slider;
+ this.allowSelection = allowSelection;
RelativeSizeAxes = Axes.Both;
@@ -42,11 +57,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
while (slider.Path.ControlPoints.Length > Pieces.Count)
{
- Pieces.Add(new PathControlPointPiece(slider, Pieces.Count)
+ var piece = new PathControlPointPiece(slider, Pieces.Count)
{
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
- RequestSelection = selectPiece
- });
+ };
+
+ if (allowSelection)
+ piece.RequestSelection = selectPiece;
+
+ Pieces.Add(piece);
}
while (slider.Path.ControlPoints.Length < Pieces.Count)
@@ -60,9 +79,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false;
}
- private void selectPiece(int index)
+ public bool OnPressed(PlatformAction action)
{
- if (inputManager.CurrentState.Keyboard.ControlPressed)
+ switch (action.ActionMethod)
+ {
+ case PlatformActionMethod.Delete:
+ return deleteSelected();
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
+
+ private void selectPiece(int index, MouseButtonEvent e)
+ {
+ if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
Pieces[index].IsSelected.Toggle();
else
{
@@ -70,5 +102,61 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
piece.IsSelected.Value = piece.Index == index;
}
}
+
+ private bool deleteSelected()
+ {
+ var newControlPoints = new List();
+
+ foreach (var piece in Pieces)
+ {
+ if (!piece.IsSelected.Value)
+ newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
+ }
+
+ // Ensure that there are any points to be deleted
+ if (newControlPoints.Count == slider.Path.ControlPoints.Length)
+ return false;
+
+ // If there are 0 remaining control points, treat the slider as being deleted
+ if (newControlPoints.Count == 0)
+ {
+ placementHandler?.Delete(slider);
+ return true;
+ }
+
+ // Make control points relative
+ Vector2 first = newControlPoints[0];
+ for (int i = 0; i < newControlPoints.Count; i++)
+ newControlPoints[i] = newControlPoints[i] - first;
+
+ // The slider's position defines the position of the first control point, and all further control points are relative to that point
+ slider.Position += first;
+
+ // Since pieces are re-used, they will not point to the deleted control points while remaining selected
+ foreach (var piece in Pieces)
+ piece.IsSelected.Value = false;
+
+ ControlPointsChanged?.Invoke(newControlPoints.ToArray());
+ return true;
+ }
+
+ public MenuItem[] ContextMenuItems
+ {
+ get
+ {
+ if (!Pieces.Any(p => p.IsHovered))
+ return null;
+
+ int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
+
+ if (selectedPoints == 0)
+ return null;
+
+ return new MenuItem[]
+ {
+ new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected())
+ };
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index d28cf7b492..78f4c4d992 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -43,5 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Size = body.Size;
OriginPosition = body.PathOffset;
}
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 6f5309c2c2..9c0afada29 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
- new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
+ new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() },
};
setState(PlacementState.Initial);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index f612ba9dfc..820d6c92d7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -1,8 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -10,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
@@ -33,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece = new SliderBodyPiece(),
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
- ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
+ ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
};
}
@@ -44,6 +51,78 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.UpdateFrom(HitObject);
}
+ private Vector2 rightClickPosition;
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Right:
+ rightClickPosition = e.MouseDownPosition;
+ return false; // Allow right click to be handled by context menu
+
+ case MouseButton.Left when e.ControlPressed && IsSelected:
+ placementControlPointIndex = addControlPoint(e.MousePosition);
+ return true; // Stop input from being handled and modifying the selection
+ }
+
+ return false;
+ }
+
+ private int? placementControlPointIndex;
+
+ protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null;
+
+ protected override bool OnDrag(DragEvent e)
+ {
+ Debug.Assert(placementControlPointIndex != null);
+
+ Vector2 position = e.MousePosition - HitObject.Position;
+
+ var controlPoints = HitObject.Path.ControlPoints.ToArray();
+ controlPoints[placementControlPointIndex.Value] = position;
+
+ onNewControlPoints(controlPoints);
+
+ return true;
+ }
+
+ protected override bool OnDragEnd(DragEndEvent e)
+ {
+ placementControlPointIndex = null;
+ return true;
+ }
+
+ private int addControlPoint(Vector2 position)
+ {
+ position -= HitObject.Position;
+
+ var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1];
+ HitObject.Path.ControlPoints.CopyTo(controlPoints);
+
+ int insertionIndex = 0;
+ float minDistance = float.MaxValue;
+
+ for (int i = 0; i < controlPoints.Length - 2; i++)
+ {
+ float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position);
+
+ if (dist < minDistance)
+ {
+ insertionIndex = i + 1;
+ minDistance = dist;
+ }
+ }
+
+ // Move the control points from the insertion index onwards to make room for the insertion
+ Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1);
+ controlPoints[insertionIndex] = position;
+
+ onNewControlPoints(controlPoints);
+
+ return insertionIndex;
+ }
+
private void onNewControlPoints(Vector2[] controlPoints)
{
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
@@ -54,6 +133,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
UpdateHitObject();
}
+ public override MenuItem[] ContextMenuItems => new MenuItem[]
+ {
+ new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
+ };
+
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index cc08d356f9..3437af8c1e 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -12,11 +16,36 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
+ ///
+ /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
+ /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
+ ///
+ private const double editor_hit_object_fade_out_extension = 500;
+
public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
+ public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h)
+ => base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState);
+
+ private void updateState(DrawableHitObject hitObject, ArmedState state)
+ {
+ switch (state)
+ {
+ case ArmedState.Miss:
+ // Get the existing fade out transform
+ var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
+ if (existing == null)
+ return;
+
+ using (hitObject.BeginAbsoluteSequence(existing.StartTime))
+ hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
+ break;
+ }
+ }
+
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One };
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
index 79cd51a7f4..9b00204d51 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
@@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
- public OsuDistanceSnapGrid(OsuHitObject hitObject)
- : base(hitObject, hitObject.StackedEndPosition)
+ public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
+ : base(hitObject, nextHitObject, hitObject.StackedEndPosition)
{
Masking = true;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index fcf2772219..812afaaa24 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.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.Linq;
using osu.Game.Beatmaps;
@@ -60,25 +61,40 @@ namespace osu.Game.Rulesets.Osu.Edit
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
+ return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
+
+ double minTime = objects.Min(h => h.StartTime);
+ return createGrid(h => h.StartTime < minTime, objects.Count + 1);
+ }
+
+ ///
+ /// Creates a grid from the last matching a predicate to a target .
+ ///
+ /// A predicate that matches s where the grid can start from.
+ /// Only the last matching the predicate is used.
+ /// An offset from the selected via at which the grid should stop.
+ /// The from a selected to a target .
+ private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1)
+ {
+ if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset));
+
+ int sourceIndex = -1;
+
+ for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++)
{
- var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
+ if (!sourceSelector(EditorBeatmap.HitObjects[i]))
+ break;
- if (lastObject == null)
- return null;
-
- return new OsuDistanceSnapGrid(lastObject);
+ sourceIndex = i;
}
- else
- {
- double minTime = objects.Min(h => h.StartTime);
- var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
+ if (sourceIndex == -1)
+ return null;
- if (lastObject == null)
- return null;
+ OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
+ OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
- return new OsuDistanceSnapGrid(lastObject);
- }
+ return new OsuDistanceSnapGrid(sourceObject, targetObject);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 472267eb66..9418565907 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -4,13 +4,34 @@
using System.Linq;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : SelectionHandler
{
- public override void HandleMovement(MoveSelectionEvent moveEvent)
+ public override bool HandleMovement(MoveSelectionEvent moveEvent)
{
+ Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
+ Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
+
+ // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
+ foreach (var h in SelectedHitObjects.OfType())
+ {
+ if (h is Spinner)
+ {
+ // Spinners don't support position adjustments
+ continue;
+ }
+
+ // Stacking is not considered
+ minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
+ maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
+ }
+
+ if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight)
+ return false;
+
foreach (var h in SelectedHitObjects.OfType())
{
if (h is Spinner)
@@ -22,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit
h.Position += moveEvent.InstantDelta;
}
- base.HandleMovement(moveEvent);
+ return true;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 1eb37f8119..63110b2797 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,7 +14,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods
};
}
- private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier;
+ private float calculateGap(float value) => Math.Clamp(value, 0, target_clamp) * targetBreakMultiplier;
// lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve
private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 7fa3dbe07e..778c2f7d43 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var destination = e.MousePosition;
FlashlightPosition = Interpolation.ValueAt(
- MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
+ Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
return base.OnMouseMove(e);
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index 80686b7983..3d566362ae 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
- var slider = hitObject as Slider;
- if (slider == null)
+ if (!(hitObject is Slider slider))
return;
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 32c9e913c6..91a4e049e3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -6,9 +6,9 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Mods
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToDrawableHitObjects(IEnumerable drawables)
{
- void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
+ static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType())
{
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
- var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime;
+ var longFadeDuration = h.GetEndTime() - fadeOutStartTime;
switch (drawable)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 9b079895fa..a9475af638 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
Vector2 originalPosition = drawable.Position;
- Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
+ Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance;
//the - 1 and + 1 prevents the hit objects to appear in the wrong position.
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index 17fcd03dd5..1664a37a66 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -55,8 +55,10 @@ namespace osu.Game.Rulesets.Osu.Mods
}
for (int i = 0; i < amountWiggles; i++)
+ {
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
wiggle();
+ }
// Keep wiggling sliders and spinners for their duration
if (!(osuObject is IHasEndTime endTime))
@@ -65,8 +67,10 @@ namespace osu.Game.Rulesets.Osu.Mods
amountWiggles = (int)(endTime.Duration / wiggle_duration);
for (int i = 0; i < amountWiggles; i++)
+ {
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
wiggle();
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs
deleted file mode 100644
index 9106f4c7bd..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Objects;
-using System.Collections.Generic;
-
-namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
-{
- ///
- /// Connects hit objects visually, for example with follow points.
- ///
- public abstract class ConnectionRenderer : LifetimeManagementContainer
- where T : HitObject
- {
- ///
- /// Hit objects to create connections for
- ///
- public abstract IEnumerable HitObjects { get; set; }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index 89ffddf4cb..7e530ca047 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -12,6 +12,9 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
+ ///
+ /// A single follow point positioned between two adjacent s.
+ ///
public class FollowPoint : Container
{
private const float width = 8;
@@ -22,11 +25,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
Origin = Anchor.Centre;
- Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
- CornerRadius = width / 2,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
new file mode 100644
index 0000000000..6c4fbbac17
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -0,0 +1,140 @@
+// 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 JetBrains.Annotations;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
+{
+ ///
+ /// Visualises the s between two s.
+ ///
+ public class FollowPointConnection : CompositeDrawable
+ {
+ // Todo: These shouldn't be constants
+ private const int spacing = 32;
+ private const double preempt = 800;
+
+ ///
+ /// The start time of .
+ ///
+ public readonly Bindable StartTime = new Bindable();
+
+ ///
+ /// The which s will exit from.
+ ///
+ [NotNull]
+ public readonly DrawableOsuHitObject Start;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The which s will exit from.
+ public FollowPointConnection([NotNull] DrawableOsuHitObject start)
+ {
+ Start = start;
+
+ RelativeSizeAxes = Axes.Both;
+
+ StartTime.BindTo(Start.HitObject.StartTimeBindable);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ bindEvents(Start);
+ }
+
+ private DrawableOsuHitObject end;
+
+ ///
+ /// The which s will enter.
+ ///
+ [CanBeNull]
+ public DrawableOsuHitObject End
+ {
+ get => end;
+ set
+ {
+ end = value;
+
+ if (end != null)
+ bindEvents(end);
+
+ if (IsLoaded)
+ scheduleRefresh();
+ else
+ refresh();
+ }
+ }
+
+ private void bindEvents(DrawableOsuHitObject drawableObject)
+ {
+ drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
+ drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
+ }
+
+ private void scheduleRefresh() => Scheduler.AddOnce(refresh);
+
+ private void refresh()
+ {
+ ClearInternal();
+
+ if (End == null)
+ return;
+
+ OsuHitObject osuStart = Start.HitObject;
+ OsuHitObject osuEnd = End.HitObject;
+
+ if (osuEnd.NewCombo)
+ return;
+
+ if (osuStart is Spinner || osuEnd is Spinner)
+ return;
+
+ Vector2 startPosition = osuStart.EndPosition;
+ Vector2 endPosition = osuEnd.Position;
+ double startTime = osuStart.GetEndTime();
+ double endTime = osuEnd.StartTime;
+
+ Vector2 distanceVector = endPosition - startPosition;
+ int distance = (int)distanceVector.Length;
+ float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
+ double duration = endTime - startTime;
+
+ for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
+ {
+ float fraction = (float)d / distance;
+ Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
+ Vector2 pointEndPosition = startPosition + fraction * distanceVector;
+ double fadeOutTime = startTime + fraction * duration;
+ double fadeInTime = fadeOutTime - preempt;
+
+ FollowPoint fp;
+
+ AddInternal(fp = new FollowPoint
+ {
+ Position = pointStartPosition,
+ Rotation = rotation,
+ Alpha = 0,
+ Scale = new Vector2(1.5f * osuEnd.Scale),
+ });
+
+ using (fp.BeginAbsoluteSequence(fadeInTime))
+ {
+ fp.FadeIn(osuEnd.TimeFadeIn);
+ fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out);
+ fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out);
+ fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn);
+ }
+
+ fp.Expire(true);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index a269b87c75..be192080f9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -1,121 +1,110 @@
// 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 osuTK;
+using System.Linq;
+using osu.Framework.Extensions;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Objects.Types;
+using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
- public class FollowPointRenderer : ConnectionRenderer
+ ///
+ /// Visualises connections between s.
+ ///
+ public class FollowPointRenderer : CompositeDrawable
{
- private int pointDistance = 32;
-
///
- /// Determines how much space there is between points.
+ /// All the s contained by this .
///
- public int PointDistance
- {
- get => pointDistance;
- set
- {
- if (pointDistance == value) return;
+ internal IReadOnlyList Connections => connections;
- pointDistance = value;
- update();
- }
- }
-
- private int preEmpt = 800;
-
- ///
- /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
- ///
- public int PreEmpt
- {
- get => preEmpt;
- set
- {
- if (preEmpt == value) return;
-
- preEmpt = value;
- update();
- }
- }
-
- private IEnumerable hitObjects;
-
- public override IEnumerable HitObjects
- {
- get => hitObjects;
- set
- {
- hitObjects = value;
- update();
- }
- }
+ private readonly List connections = new List();
public override bool RemoveCompletedTransforms => false;
- private void update()
+ ///
+ /// Adds the s around a .
+ /// This includes s leading into , and s exiting .
+ ///
+ /// The to add s for.
+ public void AddFollowPoints(DrawableOsuHitObject hitObject)
+ => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g))));
+
+ ///
+ /// Removes the s around a .
+ /// This includes s leading into , and s exiting .
+ ///
+ /// The to remove s for.
+ public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject));
+
+ ///
+ /// Adds a to this .
+ ///
+ /// The to add.
+ /// The index of in .
+ private void addConnection(FollowPointConnection connection)
{
- ClearInternal();
+ AddInternal(connection);
- if (hitObjects == null)
- return;
+ // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
+ int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
- OsuHitObject prevHitObject = null;
-
- foreach (var currHitObject in hitObjects)
+ if (index < connections.Count - 1)
{
- if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner))
- {
- Vector2 startPosition = prevHitObject.EndPosition;
- Vector2 endPosition = currHitObject.Position;
- double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime;
- double endTime = currHitObject.StartTime;
+ // Update the connection's end point to the next connection's start point
+ // h1 -> -> -> h2
+ // connection nextGroup
- Vector2 distanceVector = endPosition - startPosition;
- int distance = (int)distanceVector.Length;
- float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
- double duration = endTime - startTime;
-
- for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance)
- {
- float fraction = (float)d / distance;
- Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
- Vector2 pointEndPosition = startPosition + fraction * distanceVector;
- double fadeOutTime = startTime + fraction * duration;
- double fadeInTime = fadeOutTime - PreEmpt;
-
- FollowPoint fp;
-
- AddInternal(fp = new FollowPoint
- {
- Position = pointStartPosition,
- Rotation = rotation,
- Alpha = 0,
- Scale = new Vector2(1.5f * currHitObject.Scale),
- });
-
- using (fp.BeginAbsoluteSequence(fadeInTime))
- {
- fp.FadeIn(currHitObject.TimeFadeIn);
- fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out);
-
- fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
-
- fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
- }
-
- fp.Expire(true);
- }
- }
-
- prevHitObject = currHitObject;
+ FollowPointConnection nextConnection = connections[index + 1];
+ connection.End = nextConnection.Start;
}
+ else
+ {
+ // The end point may be non-null during re-ordering
+ connection.End = null;
+ }
+
+ if (index > 0)
+ {
+ // Update the previous connection's end point to the current connection's start point
+ // h1 -> -> -> h2
+ // prevGroup connection
+
+ FollowPointConnection previousConnection = connections[index - 1];
+ previousConnection.End = connection.Start;
+ }
+ }
+
+ ///
+ /// Removes a from this .
+ ///
+ /// The to remove.
+ /// Whether was removed.
+ private void removeGroup(FollowPointConnection connection)
+ {
+ RemoveInternal(connection);
+
+ int index = connections.IndexOf(connection);
+
+ if (index > 0)
+ {
+ // Update the previous connection's end point to the next connection's start point
+ // h1 -> -> -> h2 -> -> -> h3
+ // prevGroup connection nextGroup
+ // The current connection's end point is used since there may not be a next connection
+ FollowPointConnection previousConnection = connections[index - 1];
+ previousConnection.End = connection.End;
+ }
+
+ connections.Remove(connection);
+ }
+
+ private void onStartTimeChanged(FollowPointConnection connection)
+ {
+ // Naive but can be improved if performance becomes an issue
+ removeGroup(connection);
+ addConnection(connection);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index c46343c73c..a677cb6a72 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
private OsuInputManager osuActionInputManager;
- internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
+ internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 84d2a4af9b..71cb9a9691 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- float aimRotation = MathHelper.RadiansToDegrees((float)Math.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
+ float aimRotation = MathHelper.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
while (Math.Abs(aimRotation - Rotation) > 180)
aimRotation += aimRotation < Rotation ? 360 : -360;
@@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
else
{
// If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly).
- Rotation = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint);
+ Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 433d29f2e4..69189758a6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.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 osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
@@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking.Value = Ball.Tracking;
- double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
+ double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
Body.UpdateProgress(completionProgress);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index 66b6f0f9ac..a10c66d1df 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.Update();
- double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
+ double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!IsHit)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index d1b9ee6cb4..1261d3d19a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindTo(HitObject.PositionBindable);
}
- public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
+ public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index 210d5ff839..aab01f45d4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -16,7 +16,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Masking = true;
+
CornerRadius = Size.X / 2;
+ CornerExponent = 2;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
index c97b74756a..82e4383143 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
@@ -18,10 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- InternalChild = new Container
+ InternalChild = new CircularContainer
{
Masking = true,
- CornerRadius = Size.X / 2,
BorderThickness = 10,
BorderColour = Color4.White,
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index 70a1bad4a3..f2150280b3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
- double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
+ double end = SnakingIn.Value ? Math.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
index 9219fab830..676cefb236 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
@@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
- const int count = 18;
+ const float count = 18;
- for (int i = 0; i < count; i++)
+ for (float i = 0; i < count; i++)
{
Add(new Container
{
@@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Size = new Vector2(60, 10),
Origin = Anchor.Centre,
Position = new Vector2(
- 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f,
- 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f
+ 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
+ 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
),
- Rotation = -(float)i / count * 360 + 90,
+ Rotation = -i / count * 360 + 90,
Children = new[]
{
new Box
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index f60b7e67b2..c6f5a075e0 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects
///
internal float LazyTravelDistance;
- public List> NodeSamples { get; set; } = new List>();
+ public List> NodeSamples { get; set; } = new List>();
private int repeatCount;
@@ -108,6 +108,12 @@ namespace osu.Game.Rulesets.Osu.Objects
public HitCircle HeadCircle;
public SliderTailCircle TailCircle;
+ public Slider()
+ {
+ SamplesBindable.ItemsAdded += _ => updateNestedSamples();
+ SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
+ }
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -128,18 +134,6 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var e in
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
- var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL)
- ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
- var sampleList = new List();
-
- if (firstSample != null)
- sampleList.Add(new HitSampleInfo
- {
- Bank = firstSample.Bank,
- Volume = firstSample.Volume,
- Name = @"slidertick",
- });
-
switch (e.Type)
{
case SliderEventType.Tick:
@@ -151,7 +145,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,
Scale = Scale,
- Samples = sampleList
});
break;
@@ -161,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = e.Time,
Position = Position,
StackHeight = StackHeight,
- Samples = getNodeSamples(0),
SampleControlPoint = SampleControlPoint,
});
break;
@@ -187,11 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,
Scale = Scale,
- Samples = getNodeSamples(e.SpanIndex + 1)
});
break;
}
}
+
+ updateNestedSamples();
}
private void updateNestedPositions()
@@ -203,7 +196,33 @@ namespace osu.Game.Rulesets.Osu.Objects
TailCircle.Position = EndPosition;
}
- private List getNodeSamples(int nodeIndex) =>
+ private void updateNestedSamples()
+ {
+ var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
+ ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
+ var sampleList = new List();
+
+ if (firstSample != null)
+ {
+ sampleList.Add(new HitSampleInfo
+ {
+ Bank = firstSample.Bank,
+ Volume = firstSample.Volume,
+ Name = @"slidertick",
+ });
+ }
+
+ foreach (var tick in NestedHitObjects.OfType())
+ tick.Samples = sampleList;
+
+ foreach (var repeat in NestedHitObjects.OfType())
+ repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
+
+ if (HeadCircle != null)
+ HeadCircle.Samples = getNodeSamples(0);
+ }
+
+ private IList getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index 24320b6579..bd59e8a03f 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -10,7 +10,7 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
-using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Replays
private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
{
- double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
+ double endTime = prev.GetEndTime();
HitWindows hitWindows = null;
@@ -185,14 +185,14 @@ namespace osu.Game.Rulesets.Osu.Replays
{
Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos;
float distFromCentre = spinCentreOffset.Length;
- float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
+ float distToTangentPoint = MathF.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
if (distFromCentre > SPIN_RADIUS)
{
// Previous cursor position was outside spin circle, set startPosition to the tangent point.
// Angle between centre offset and tangent point offset.
- float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre);
+ float angle = MathF.Asin(SPIN_RADIUS / distFromCentre);
if (angle > 0)
{
@@ -204,8 +204,8 @@ namespace osu.Game.Rulesets.Osu.Replays
}
// Rotate by angle so it's parallel to tangent line
- spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle);
- spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle);
+ spinCentreOffset.X = spinCentreOffset.X * MathF.Cos(angle) - spinCentreOffset.Y * MathF.Sin(angle);
+ spinCentreOffset.Y = spinCentreOffset.X * MathF.Sin(angle) + spinCentreOffset.Y * MathF.Cos(angle);
// Set length to distToTangentPoint
spinCentreOffset.Normalize();
@@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Osu.Replays
var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
- double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
+ double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
int endDelay = h is Spinner ? 1 : 0;
var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
@@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Replays
Vector2 difference = startPosition - SPINNER_CENTRE;
float radius = difference.Length;
- float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
+ float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X);
double t;
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 69e53d6eea..6d1ea4bbfc 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI;
-using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
private readonly ApproachCircleProxyContainer approachCircles;
private readonly JudgementContainer judgementLayer;
- private readonly ConnectionRenderer connectionLayer;
+ private readonly FollowPointRenderer followPoints;
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
InternalChildren = new Drawable[]
{
- connectionLayer = new FollowPointRenderer
+ followPoints = new FollowPointRenderer
{
RelativeSizeAxes = Axes.Both,
Depth = 2,
@@ -64,11 +63,18 @@ namespace osu.Game.Rulesets.Osu.UI
};
base.Add(h);
+
+ followPoints.AddFollowPoints((DrawableOsuHitObject)h);
}
- public override void PostProcess()
+ public override bool Remove(DrawableHitObject h)
{
- connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType();
+ bool result = base.Remove(h);
+
+ if (result)
+ followPoints.RemoveFollowPoints((DrawableOsuHitObject)h);
+
+ return result;
}
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index b0ca314551..bffeaabb55 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -1,9 +1,7 @@
-
- netstandard2.0
+ netstandard2.1
Library
- AnyCPU
true
click the circles. to the beat.
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
index cd4b74aa16..d9de0fde4e 100644
--- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
index 3e46bb89af..8ee640cd99 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
@@ -1,6 +1,5 @@
-
+
-
Debug
iPhoneSimulator
@@ -33,5 +32,4 @@
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 6c1882b4e2..28f5d4d301 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Tests.Beatmaps;
@@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
yield return new ConvertValue
{
StartTime = hitObject.StartTime,
- EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+ EndTime = hitObject.GetEndTime(),
IsRim = hitObject is RimHit,
IsCentre = hitObject is CentreHit,
IsDrumRoll = hitObject is DrumRoll,
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 717e795112..b5bd384e05 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index f0cf8d9c7d..10cc861b7e 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -73,127 +73,133 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
- var distanceData = obj as IHasDistance;
- var repeatsData = obj as IHasRepeats;
- var endTimeData = obj as IHasEndTime;
- var curveData = obj as IHasCurve;
-
// Old osu! used hit sounding to determine various hit type information
- List samples = obj.Samples;
+ IList samples = obj.Samples;
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
- if (distanceData != null)
+ switch (obj)
{
- // Number of spans of the object - one for the initial length and for each repeat
- int spans = repeatsData?.SpanCount() ?? 1;
-
- TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
- DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
-
- double speedAdjustment = difficultyPoint.SpeedMultiplier;
- double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
-
- // The true distance, accounting for any repeats. This ends up being the drum roll distance later
- double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
-
- // The velocity of the taiko hit object - calculated as the velocity of a drum roll
- double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
- // The duration of the taiko hit object
- double taikoDuration = distance / taikoVelocity;
-
- // The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
- // The duration of the osu! hit object
- double osuDuration = distance / osuVelocity;
-
- // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
- // only uses it for tick rate if beatmap version < 8
- if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
- speedAdjustedBeatLength *= speedAdjustment;
-
- // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
- double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
-
- if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
+ case IHasDistance distanceData:
{
- List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples });
+ // Number of spans of the object - one for the initial length and for each repeat
+ int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
- int i = 0;
+ TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
+ DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
- for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
+ double speedAdjustment = difficultyPoint.SpeedMultiplier;
+ double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
+
+ // The true distance, accounting for any repeats. This ends up being the drum roll distance later
+ double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
+
+ // The velocity of the taiko hit object - calculated as the velocity of a drum roll
+ double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
+ // The duration of the taiko hit object
+ double taikoDuration = distance / taikoVelocity;
+
+ // The velocity of the osu! hit object - calculated as the velocity of a slider
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
+ // The duration of the osu! hit object
+ double osuDuration = distance / osuVelocity;
+
+ // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
+ // only uses it for tick rate if beatmap version < 8
+ if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
+ speedAdjustedBeatLength *= speedAdjustment;
+
+ // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
+ double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
+
+ if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
- List currentSamples = allSamples[i];
- bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
- strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
+ List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples });
- if (isRim)
- {
- yield return new RimHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
- else
- {
- yield return new CentreHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
+ int i = 0;
- i = (i + 1) % allSamples.Count;
+ for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
+ {
+ IList currentSamples = allSamples[i];
+ bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
+ strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
+
+ if (isRim)
+ {
+ yield return new RimHit
+ {
+ StartTime = j,
+ Samples = currentSamples,
+ IsStrong = strong
+ };
+ }
+ else
+ {
+ yield return new CentreHit
+ {
+ StartTime = j,
+ Samples = currentSamples,
+ IsStrong = strong
+ };
+ }
+
+ i = (i + 1) % allSamples.Count;
+ }
}
- }
- else
- {
- yield return new DrumRoll
+ else
{
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong,
- Duration = taikoDuration,
- TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
- };
- }
- }
- else if (endTimeData != null)
- {
- double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
+ yield return new DrumRoll
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ IsStrong = strong,
+ Duration = taikoDuration,
+ TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
+ };
+ }
- yield return new Swell
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- Duration = endTimeData.Duration,
- RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
- };
- }
- else
- {
- bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
-
- if (isRim)
- {
- yield return new RimHit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong
- };
+ break;
}
- else
+
+ case IHasEndTime endTimeData:
{
- yield return new CentreHit
+ double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
+
+ yield return new Swell
{
StartTime = obj.StartTime,
Samples = obj.Samples,
- IsStrong = strong
+ Duration = endTimeData.Duration,
+ RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
};
+
+ break;
+ }
+
+ default:
+ {
+ bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
+
+ if (isRim)
+ {
+ yield return new RimHit
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ IsStrong = strong
+ };
+ }
+ else
+ {
+ yield return new CentreHit
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ IsStrong = strong
+ };
+ }
+
+ break;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 70249db0f6..c3638253e4 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more
- double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
+ double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
strainValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index cc0d6829ba..338fd9e20f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -1,12 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
using osuTK.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Framework.Graphics;
@@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
else
rollingHits--;
- rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
+ rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
MainPiece.FadeAccent(newColour, 100);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 9c9dfc5f9e..fa39819199 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
-using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
@@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
var completion = (float)numHits / HitObject.RequiredHits;
expandingRing
- .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
+ .FadeTo(expandingRing.Alpha + Math.Clamp(completion / 16, 0.1f, 0.6f), 50)
.Then()
.FadeTo(completion / 8, 2000, Easing.OutQuint);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index 773e3ae907..8067054f8f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -10,27 +10,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
{
- private Color4 accentColour;
-
///
/// The colour of the inner circle and outer glows.
///
- public virtual Color4 AccentColour
- {
- get => accentColour;
- set => accentColour = value;
- }
-
- private bool kiaiMode;
+ public virtual Color4 AccentColour { get; set; }
///
/// Whether Kiai mode effects are enabled for this circle piece.
///
- public virtual bool KiaiMode
- {
- get => kiaiMode;
- set => kiaiMode = value;
- }
+ public virtual bool KiaiMode { get; set; }
public TaikoPiece()
{
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 299679b2c1..e61953aeb8 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -43,76 +43,83 @@ namespace osu.Game.Rulesets.Taiko.Replays
IHasEndTime endTimeData = h as IHasEndTime;
double endTime = endTimeData?.EndTime ?? h.StartTime;
- Swell swell = h as Swell;
- DrumRoll drumRoll = h as DrumRoll;
- Hit hit = h as Hit;
-
- if (swell != null)
+ switch (h)
{
- int d = 0;
- int count = 0;
- int req = swell.RequiredHits;
- double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
-
- for (double j = h.StartTime; j < endTime; j += hitRate)
+ case Swell swell:
{
- TaikoAction action;
+ int d = 0;
+ int count = 0;
+ int req = swell.RequiredHits;
+ double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
- switch (d)
+ for (double j = h.StartTime; j < endTime; j += hitRate)
{
- default:
- case 0:
- action = TaikoAction.LeftCentre;
- break;
+ TaikoAction action;
- case 1:
- action = TaikoAction.LeftRim;
- break;
+ switch (d)
+ {
+ default:
+ case 0:
+ action = TaikoAction.LeftCentre;
+ break;
- case 2:
- action = TaikoAction.RightCentre;
- break;
+ case 1:
+ action = TaikoAction.LeftRim;
+ break;
- case 3:
- action = TaikoAction.RightRim;
+ case 2:
+ action = TaikoAction.RightCentre;
+ break;
+
+ case 3:
+ action = TaikoAction.RightRim;
+ break;
+ }
+
+ Frames.Add(new TaikoReplayFrame(j, action));
+ d = (d + 1) % 4;
+ if (++count == req)
break;
}
- Frames.Add(new TaikoReplayFrame(j, action));
- d = (d + 1) % 4;
- if (++count == req)
- break;
- }
- }
- else if (drumRoll != null)
- {
- foreach (var tick in drumRoll.NestedHitObjects.OfType())
- {
- Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
- hitButton = !hitButton;
- }
- }
- else if (hit != null)
- {
- TaikoAction[] actions;
-
- if (hit is CentreHit)
- {
- actions = h.IsStrong
- ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
- : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
- }
- else
- {
- actions = h.IsStrong
- ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
- : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
+ break;
}
- Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
+ case DrumRoll drumRoll:
+ {
+ foreach (var tick in drumRoll.NestedHitObjects.OfType())
+ {
+ Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
+ hitButton = !hitButton;
+ }
+
+ break;
+ }
+
+ case Hit hit:
+ {
+ TaikoAction[] actions;
+
+ if (hit is CentreHit)
+ {
+ actions = h.IsStrong
+ ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
+ : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
+ }
+ else
+ {
+ actions = h.IsStrong
+ ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
+ : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
+ }
+
+ Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
+ break;
+ }
+
+ default:
+ throw new InvalidOperationException("Unknown hit object type.");
}
- else
- throw new InvalidOperationException("Unknown hit object type.");
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
index 84464b199e..980f5ea340 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.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 osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
base.Update();
- float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
+ float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
Size = new Vector2(1, default_relative_height * aspectAdjust);
}
}
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index 656ebcc7c2..ebed8c6d7c 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -1,9 +1,7 @@
-
- netstandard2.0
+ netstandard2.1
Library
- AnyCPU
true
bash the drum. to the beat.
diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/Properties/AndroidManifest.xml
index bb996dc5ca..4a63f0c357 100644
--- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Tests.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
index 5c0713b895..ca68369ebb 100644
--- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
+++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
@@ -1,6 +1,5 @@
-
+
-
Debug
iPhoneSimulator
@@ -48,5 +47,4 @@
-
\ No newline at end of file
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 2ecc516919..26e70f19e4 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -413,7 +413,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
}
- HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@@ -431,7 +431,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
}
- HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@@ -451,7 +451,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
- HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs
index b3863bcf44..669acc3202 100644
--- a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
try
{
- var _ = int.Parse(input);
+ _ = int.Parse(input);
}
catch (Exception e)
{
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 4e81954f50..4766411cbd 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -411,6 +411,48 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [Test]
+ public async Task TestImportWithDuplicateHashes()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var temp = TestResources.GetTestBeatmapForImport();
+
+ string extractedFolder = $"{temp}_extracted";
+ Directory.CreateDirectory(extractedFolder);
+
+ try
+ {
+ using (var zip = ZipArchive.Open(temp))
+ zip.WriteToDirectory(extractedFolder);
+
+ using (var zip = ZipArchive.Create())
+ {
+ zip.AddAllFromDirectory(extractedFolder);
+ zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
+ zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
+ }
+
+ await osu.Dependencies.Get().Import(temp);
+
+ ensureLoaded(osu);
+ }
+ finally
+ {
+ Directory.Delete(extractedFolder, true);
+ }
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
[Test]
public async Task TestImportNestedStructure()
{
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index 9869ddde41..7b2913b817 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsNull(filterCriteria.BPM.Max);
}
- private static object[] lengthQueryExamples =
+ private static readonly object[] length_query_examples =
{
new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
};
[Test]
- [TestCaseSource(nameof(lengthQueryExamples))]
+ [TestCaseSource(nameof(length_query_examples))]
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
{
string query = $"length={lengthQuery} time";
diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
index 18cbd4e7c5..7df7df22ea 100644
--- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
+++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
@@ -225,8 +225,10 @@ namespace osu.Game.Tests.NonVisual
private void fastForwardToPoint(double destination)
{
for (int i = 0; i < 1000; i++)
+ {
if (handler.SetFrameFromTime(destination) == null)
return;
+ }
throw new TimeoutException("Seek was never fulfilled");
}
diff --git a/osu.Game.Tests/Resources/skin-20.ini b/osu.Game.Tests/Resources/skin-20.ini
new file mode 100644
index 0000000000..947b56b2f9
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin-20.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 2
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/skin-latest.ini b/osu.Game.Tests/Resources/skin-latest.ini
new file mode 100644
index 0000000000..32f500263f
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin-latest.ini
@@ -0,0 +1,2 @@
+[General]
+Version: latest
\ No newline at end of file
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index 0d96dd08da..f68d49dd3e 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -25,7 +25,9 @@ namespace osu.Game.Tests.Skins
var comboColors = decoder.Decode(stream).ComboColours;
List expectedColors;
+
if (hasColours)
+ {
expectedColors = new List
{
new Color4(142, 199, 255, 255),
@@ -33,6 +35,7 @@ namespace osu.Game.Tests.Skins
new Color4(128, 255, 255, 255),
new Color4(100, 100, 100, 100),
};
+ }
else
expectedColors = new DefaultSkin().Configuration.ComboColours;
@@ -56,5 +59,32 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
}
}
+
+ [Test]
+ public void TestDecodeSpecifiedVersion()
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource("skin-20.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
+ }
+
+ [Test]
+ public void TestDecodeLatestVersion()
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource("skin-latest.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ Assert.AreEqual(LegacySkinConfiguration.LATEST_VERSION, decoder.Decode(stream).LegacyVersion);
+ }
+
+ [Test]
+ public void TestDecodeNoVersion()
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ Assert.IsNull(decoder.Decode(stream).LegacyVersion);
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 578030748b..8b9c648442 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -116,6 +116,14 @@ namespace osu.Game.Tests.Skins
});
}
+ [Test]
+ public void TestLegacyVersionLookup()
+ {
+ AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m);
+ AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null);
+ AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
+ }
+
public enum LookupType
{
Test
diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
index df6740421b..d76905dab8 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
@@ -3,16 +3,20 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
+using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner
{
- private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
+ private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager();
+
+ private AudioManager audio;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -23,8 +27,10 @@ namespace osu.Game.Tests.Visual.Components
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audio)
{
+ this.audio = audio;
+
Add(trackManager);
}
@@ -34,6 +40,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
+ AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddAssert("started", () => track.IsRunning);
AddStep("stop", () => track.Stop());
@@ -52,10 +59,15 @@ namespace osu.Game.Tests.Visual.Components
track2 = getOwnedTrack();
});
+ AddUntilStep("wait loaded", () => track1.IsLoaded && track2.IsLoaded);
+
AddStep("start track 1", () => track1.Start());
AddStep("start track 2", () => track2.Start());
AddAssert("track 1 stopped", () => !track1.IsRunning);
AddAssert("track 2 started", () => track2.IsRunning);
+ AddStep("start track 1", () => track1.Start());
+ AddAssert("track 2 stopped", () => !track2.IsRunning);
+ AddAssert("track 1 started", () => track1.IsRunning);
}
[Test]
@@ -64,6 +76,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
+ AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("stop by owner", () => trackManager.StopAnyPlaying(this));
AddAssert("stopped", () => !track.IsRunning);
@@ -76,6 +89,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
+ AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
AddAssert("not stopped", () => track.IsRunning);
@@ -83,22 +97,100 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("stopped", () => !track.IsRunning);
}
- private PreviewTrack getTrack() => trackManager.Get(null);
+ [Test]
+ public void TestNonPresentTrack()
+ {
+ TestPreviewTrack track = null;
- private PreviewTrack getOwnedTrack()
+ AddStep("get non-present track", () =>
+ {
+ Add(new TestTrackOwner(track = getTrack()));
+ track.Alpha = 0;
+ });
+ AddUntilStep("wait loaded", () => track.IsLoaded);
+ AddStep("start", () => track.Start());
+ AddStep("seek to end", () => track.Track.Seek(track.Track.Length));
+ AddAssert("track stopped", () => !track.IsRunning);
+ }
+
+ ///
+ /// Ensures that changes correctly.
+ ///
+ [Test]
+ public void TestCurrentTrackChanges()
+ {
+ PreviewTrack track = null;
+ TestTrackOwner owner = null;
+
+ AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
+ AddUntilStep("wait loaded", () => track.IsLoaded);
+ AddStep("start track", () => track.Start());
+ AddAssert("current is track", () => trackManager.CurrentTrack == track);
+ AddStep("pause manager updates", () => trackManager.AllowUpdate = false);
+ AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
+ AddAssert("current not changed", () => trackManager.CurrentTrack == track);
+ AddStep("resume manager updates", () => trackManager.AllowUpdate = true);
+ AddAssert("current is null", () => trackManager.CurrentTrack == null);
+ }
+
+ ///
+ /// Ensures that mutes game-wide audio tracks correctly.
+ ///
+ [TestCase(false)]
+ [TestCase(true)]
+ public void TestEnsureMutingCorrectly(bool stopAnyPlaying)
+ {
+ PreviewTrack track = null;
+ TestTrackOwner owner = null;
+
+ AddStep("ensure volume not zero", () =>
+ {
+ if (audio.Volume.Value == 0)
+ audio.Volume.Value = 1;
+
+ if (audio.VolumeTrack.Value == 0)
+ audio.VolumeTrack.Value = 1;
+ });
+
+ AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
+
+ AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
+ AddUntilStep("wait loaded", () => track.IsLoaded);
+ AddStep("start track", () => track.Start());
+ AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0);
+
+ if (stopAnyPlaying)
+ AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
+ else
+ AddStep("stop track", () => track.Stop());
+
+ AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
+ }
+
+ private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
+
+ private TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
- Add(track);
+ LoadComponentAsync(track, Add);
return track;
}
private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner
{
+ private readonly PreviewTrack track;
+
public TestTrackOwner(PreviewTrack track)
{
- AddInternal(track);
+ this.track = track;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ LoadComponentAsync(track, AddInternal);
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@@ -109,14 +201,28 @@ namespace osu.Game.Tests.Visual.Components
}
}
- private class TestPreviewTrackManager : PreviewTrackManager
+ public class TestPreviewTrackManager : PreviewTrackManager
{
+ public bool AllowUpdate = true;
+
+ public new PreviewTrack CurrentTrack => base.CurrentTrack;
+
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
- protected class TestPreviewTrack : TrackManagerPreviewTrack
+ public override bool UpdateSubTree()
+ {
+ if (!AllowUpdate)
+ return true;
+
+ return base.UpdateSubTree();
+ }
+
+ public class TestPreviewTrack : TrackManagerPreviewTrack
{
private readonly ITrackStore trackManager;
+ public new Track Track => base.Track;
+
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
: base(beatmapSetInfo, trackManager)
{
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
index b8c31d5dbb..e4c987923c 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
@@ -32,7 +32,11 @@ namespace osu.Game.Tests.Visual.Editor
{
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
+ }
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
Children = new Drawable[]
{
new Box
@@ -42,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editor
},
new TestDistanceSnapGrid(new HitObject(), grid_position)
};
- }
+ });
[TestCase(1)]
[TestCase(2)]
@@ -57,12 +61,29 @@ namespace osu.Game.Tests.Visual.Editor
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
}
+ [Test]
+ public void TestLimitedDistance()
+ {
+ AddStep("create limited grid", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
+ };
+ });
+ }
+
private class TestDistanceSnapGrid : DistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
- public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
- : base(hitObject, centrePosition)
+ public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
+ : base(hitObject, nextHitObject, centrePosition)
{
}
@@ -77,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editor
int beatIndex = 0;
- for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++)
+ for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
@@ -90,7 +111,7 @@ namespace osu.Game.Tests.Visual.Editor
beatIndex = 0;
- for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
+ for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
@@ -103,7 +124,7 @@ namespace osu.Game.Tests.Visual.Editor
beatIndex = 0;
- for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++)
+ for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
@@ -116,7 +137,7 @@ namespace osu.Game.Tests.Visual.Editor
beatIndex = 0;
- for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
+ for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
@@ -138,9 +159,9 @@ namespace osu.Game.Tests.Visual.Editor
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
- public float DurationToDistance(double referenceTime, double duration) => 0;
+ public float DurationToDistance(double referenceTime, double duration) => (float)duration;
- public double DistanceToDuration(double referenceTime, float distance) => 0;
+ public double DistanceToDuration(double referenceTime, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
index a8c2362910..6e5b3b93e9 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
@@ -10,9 +10,9 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
@@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor
}
}
- private class StartStopButton : Button
+ private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
private bool started;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
new file mode 100644
index 0000000000..121853d8d0
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
@@ -0,0 +1,35 @@
+// 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.Allocation;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit.Timing;
+
+namespace osu.Game.Tests.Visual.Editor
+{
+ [TestFixture]
+ public class TestSceneTimingScreen : EditorClockTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ControlPointTable),
+ typeof(ControlPointSettings),
+ typeof(Section<>),
+ typeof(TimingSection),
+ typeof(EffectSection),
+ typeof(SampleSection),
+ typeof(DifficultySection),
+ typeof(RowAttribute)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Child = new TimingScreen();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index f94071a7a9..5ee109e3dd 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -7,6 +7,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -29,9 +30,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
}
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
- var working = base.CreateWorkingBeatmap(beatmap);
+ var working = base.CreateWorkingBeatmap(beatmap, storyboard);
track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
index a934d22b5d..e3688c276f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Framework.MathUtils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
@@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both,
Children = new[]
{
- new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
- new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
- new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
+ new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
+ new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
+ new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
}
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
index 879e15c548..19dce303ea 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
@@ -95,6 +95,19 @@ namespace osu.Game.Tests.Visual.Gameplay
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeforeGameplayStart(bool withBreaks)
+ {
+ setClock(true);
+
+ if (withBreaks)
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ seekAndAssertBreak("seek to break intro time", -100, true);
+ seekAndAssertBreak("seek to break intro time", 0, false);
+ }
+
private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index ffc025a942..b2b58a63fb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
@@ -35,9 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private Track track;
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
- var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = working.Track;
return working;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
new file mode 100644
index 0000000000..0150c6ea74
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -0,0 +1,113 @@
+// 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.Graphics;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
+using osu.Game.Tests.Beatmaps;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneLeadIn : RateAdjustedBeatmapTestScene
+ {
+ private LeadInPlayer player;
+
+ private const double lenience_ms = 10;
+
+ private const double first_hit_object = 2170;
+
+ [TestCase(1000, 0)]
+ [TestCase(2000, 0)]
+ [TestCase(3000, first_hit_object - 3000)]
+ [TestCase(10000, first_hit_object - 10000)]
+ public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime)
+ {
+ loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
+ {
+ BeatmapInfo = { AudioLeadIn = leadIn }
+ });
+
+ AddAssert($"first frame is {expectedStartTime}", () =>
+ {
+ Debug.Assert(player.FirstFrameClockTime != null);
+ return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
+ });
+ }
+
+ [TestCase(1000, 0)]
+ [TestCase(0, 0)]
+ [TestCase(-1000, -1000)]
+ [TestCase(-10000, -10000)]
+ public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
+ {
+ var storyboard = new Storyboard();
+
+ var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
+ sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
+
+ storyboard.GetLayer("Background").Add(sprite);
+
+ loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
+
+ AddAssert($"first frame is {expectedStartTime}", () =>
+ {
+ Debug.Assert(player.FirstFrameClockTime != null);
+ return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
+ });
+ }
+
+ private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ {
+ AddStep("create player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard);
+ LoadScreen(player = new LeadInPlayer());
+ });
+
+ AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1);
+ }
+
+ private class LeadInPlayer : TestPlayer
+ {
+ public LeadInPlayer()
+ : base(false, false)
+ {
+ }
+
+ public double? FirstFrameClockTime;
+
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
+ public double GameplayStartTime => DrawableRuleset.GameplayStartTime;
+
+ public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
+
+ public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ if (!FirstFrameClockTime.HasValue)
+ {
+ FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
+ AddInternal(new OsuSpriteText
+ {
+ Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
+ + $"FirstHitObjectTime: {FirstHitObjectTime} "
+ + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} "
+ + $"FirstFrameClockTime: {FirstFrameClockTime}"
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 64022b2410..6e8975f11b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -237,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
AddStep("exit", () => Player.Exit());
confirmExited();
+ confirmNoTrackAdjustments();
}
private void confirmPaused()
@@ -258,6 +259,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
}
+ private void confirmNoTrackAdjustments()
+ {
+ AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
+ }
+
private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void resume() => AddStep("resume", () => Player.Resume());
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs
index 65b56319e8..4d701f56a9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs
@@ -6,6 +6,7 @@ using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
- var working = base.CreateWorkingBeatmap(beatmap);
+ var working = base.CreateWorkingBeatmap(beatmap, storyboard);
workingWeakReferences.Add(working);
return working;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index b3d4820737..ec94053679 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -326,21 +326,23 @@ namespace osu.Game.Tests.Visual.Gameplay
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
- public event Action SourceChanged;
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
}
private class TestSkinComponent : ISkinComponent
{
- private readonly string name;
-
public TestSkinComponent(string name)
{
- this.name = name;
+ LookupName = name;
}
public string ComponentGroup => string.Empty;
- public string LookupName => name;
+ public string LookupName { get; }
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index b152c21454..875e7b9758 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -4,8 +4,8 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Timing;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
@@ -18,25 +18,37 @@ namespace osu.Game.Tests.Visual.Gameplay
private SkipOverlay skip;
private int requestCount;
+ private double increment;
+
+ private GameplayClockContainer gameplayClockContainer;
+ private GameplayClock gameplayClock;
+
+ private const double skip_time = 6000;
+
[SetUp]
public void SetUp() => Schedule(() =>
{
requestCount = 0;
- Child = new Container
+ increment = skip_time;
+
+ Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
{
RelativeSizeAxes = Axes.Both,
- Clock = new FramedOffsetClock(Clock)
- {
- Offset = -Clock.CurrentTime,
- },
Children = new Drawable[]
{
- skip = new SkipOverlay(6000)
+ skip = new SkipOverlay(skip_time)
{
- RequestSeek = _ => requestCount++
+ RequestSkip = () =>
+ {
+ requestCount++;
+ gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment);
+ }
}
},
};
+
+ gameplayClockContainer.Start();
+ gameplayClock = gameplayClockContainer.GameplayClock;
});
[Test]
@@ -64,19 +76,35 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestClickOnlyActuatesOnce()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
- AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () =>
+ {
+ increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2;
+ InputManager.Click(MouseButton.Left);
+ });
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
+ [Test]
+ public void TestClickOnlyActuatesMultipleTimes()
+ {
+ AddStep("set increment lower", () => increment = 3000);
+ AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkRequestCount(2);
+ }
+
[Test]
public void TestDoesntFadeOnMouseDown()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
- AddUntilStep("wait for overlay disapper", () => !skip.IsAlive);
+ AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 286971bc90..5ca2c9868f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem),
+ typeof(NotSupporterPlaceholder)
};
protected override bool UseOnlineAPI => true;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
index 05f5c117e4..80fad44593 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
- BeatmapInfo createBeatmap() => new BeatmapInfo
+ static BeatmapInfo createBeatmap() => new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs
new file mode 100644
index 0000000000..8e2ee4e28d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet.Buttons;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneFavouriteButton : OsuTestScene
+ {
+ private FavouriteButton favourite;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create button", () => Child = favourite = new FavouriteButton
+ {
+ RelativeSizeAxes = Axes.None,
+ Size = new Vector2(50),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+
+ [Test]
+ public void TestLoggedOutIn()
+ {
+ AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
+ AddStep("log out", () => API.Logout());
+ checkEnabled(false);
+ AddStep("log in", () => API.Login("test", "test"));
+ checkEnabled(true);
+ }
+
+ [Test]
+ public void TestBeatmapChange()
+ {
+ AddStep("log in", () => API.Login("test", "test"));
+ AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
+ checkEnabled(true);
+ AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo());
+ checkEnabled(false);
+ }
+
+ private void checkEnabled(bool expected)
+ {
+ AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite.Enabled.Value == expected);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs
new file mode 100644
index 0000000000..e0e5a088ce
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs
@@ -0,0 +1,78 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Overlays.BeatmapSet;
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Catch;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Bindables;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneLeaderboardModSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(LeaderboardModSelector),
+ };
+
+ public TestSceneLeaderboardModSelector()
+ {
+ LeaderboardModSelector modSelector;
+ FillFlowContainer selectedMods;
+ var ruleset = new Bindable();
+
+ Add(selectedMods = new FillFlowContainer
+ {
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ });
+
+ Add(modSelector = new LeaderboardModSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Ruleset = { BindTarget = ruleset }
+ });
+
+ modSelector.SelectedMods.ItemsAdded += mods =>
+ {
+ mods.ForEach(mod => selectedMods.Add(new OsuSpriteText
+ {
+ Text = mod.Acronym,
+ }));
+ };
+
+ modSelector.SelectedMods.ItemsRemoved += mods =>
+ {
+ mods.ForEach(mod =>
+ {
+ foreach (var selected in selectedMods)
+ {
+ if (selected.Text == mod.Acronym)
+ {
+ selectedMods.Remove(selected);
+ break;
+ }
+ }
+ });
+ };
+
+ AddStep("osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo);
+ AddStep("mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo);
+ AddStep("taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo);
+ AddStep("catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo);
+ AddStep("Deselect all", () => modSelector.DeselectAll());
+ AddStep("null ruleset", () => ruleset.Value = null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
new file mode 100644
index 0000000000..546f6ac182
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Overlays;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneNewsOverlay : OsuTestScene
+ {
+ private NewsOverlay news;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Add(news = new NewsOverlay());
+ AddStep(@"Show", news.Show);
+ AddStep(@"Hide", news.Hide);
+
+ AddStep(@"Show front page", () => news.ShowFrontPage());
+ AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
index db6afa9bf3..cd954cd6bd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Rankings;
using osu.Game.Users;
using osuTK;
@@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online
Size = new Vector2(30, 20),
Country = countryA,
},
- text = new SpriteText
+ text = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
index 01400bf1d9..28b5693ef4 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
@@ -130,12 +130,14 @@ namespace osu.Game.Tests.Visual.Online
AddRepeatStep("add many messages", () =>
{
for (int i = 0; i < messages_per_call; i++)
+ {
testChannel.AddNewMessages(new Message(sequence++)
{
Sender = longUsernameUser,
Content = "Many messages! " + Guid.NewGuid(),
Timestamp = DateTimeOffset.Now
});
+ }
}, Channel.MAX_HISTORY / messages_per_call + 5);
AddAssert("Ensure no adjacent day separators", () =>
@@ -143,8 +145,10 @@ namespace osu.Game.Tests.Visual.Online
var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
foreach (var i in indices)
+ {
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator)
return false;
+ }
return true;
});
diff --git a/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs
new file mode 100644
index 0000000000..4702d24125
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Bindables;
+using osu.Game.Overlays.Comments;
+using osu.Framework.MathUtils;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneTotalCommentsCounter : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TotalCommentsCounter),
+ };
+
+ public TestSceneTotalCommentsCounter()
+ {
+ var count = new BindableInt();
+
+ Add(new TotalCommentsCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = count }
+ });
+
+ AddStep(@"Set 100", () => count.Value = 100);
+ AddStep(@"Set 0", () => count.Value = 0);
+ AddStep(@"Set random", () => count.Value = RNG.Next(0, int.MaxValue));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs
index 18d6028cb8..0f41247571 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRequest.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Users;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Taiko;
using osu.Game.Graphics.UserInterface;
@@ -94,11 +94,11 @@ namespace osu.Game.Tests.Visual.Online
AddRange(new Drawable[]
{
- new SpriteText
+ new OsuSpriteText
{
Text = $@"Username: {user.NewValue?.Username}"
},
- new SpriteText
+ new OsuSpriteText
{
Text = $@"RankedScore: {user.NewValue?.Statistics.RankedScore}"
},
diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs
new file mode 100644
index 0000000000..8197cf72de
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs
@@ -0,0 +1,76 @@
+// 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.Graphics;
+using osu.Game.Overlays.Comments;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneVotePill : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(VotePill)
+ };
+
+ private VotePill votePill;
+
+ [Test]
+ public void TestUserCommentPill()
+ {
+ AddStep("Log in", logIn);
+ AddStep("User comment", () => addVotePill(getUserComment()));
+ AddStep("Click", () => votePill.Click());
+ AddAssert("Not loading", () => !votePill.IsLoading);
+ }
+
+ [Test]
+ public void TestRandomCommentPill()
+ {
+ AddStep("Log in", logIn);
+ AddStep("Random comment", () => addVotePill(getRandomComment()));
+ AddStep("Click", () => votePill.Click());
+ AddAssert("Loading", () => votePill.IsLoading);
+ }
+
+ [Test]
+ public void TestOfflineRandomCommentPill()
+ {
+ AddStep("Log out", API.Logout);
+ AddStep("Random comment", () => addVotePill(getRandomComment()));
+ AddStep("Click", () => votePill.Click());
+ AddAssert("Not loading", () => !votePill.IsLoading);
+ }
+
+ private void logIn() => API.Login("localUser", "password");
+
+ private Comment getUserComment() => new Comment
+ {
+ IsVoted = false,
+ UserId = API.LocalUser.Value.Id,
+ VotesCount = 10,
+ };
+
+ private Comment getRandomComment() => new Comment
+ {
+ IsVoted = false,
+ UserId = 4444,
+ VotesCount = 2,
+ };
+
+ private void addVotePill(Comment comment)
+ {
+ Clear();
+ Add(votePill = new VotePill(comment)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 8b82567a8d..132b104afb 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -10,6 +10,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@@ -51,11 +52,6 @@ namespace osu.Game.Tests.Visual.SongSelect
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
-
- Add(carousel = new TestBeatmapCarousel
- {
- RelativeSizeAxes = Axes.Both,
- });
}
///
@@ -338,10 +334,19 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestHiding()
{
- BeatmapSetInfo hidingSet = createTestBeatmapSet(1);
- hidingSet.Beatmaps[1].Hidden = true;
+ BeatmapSetInfo hidingSet = null;
+ List hiddenList = new List();
- loadBeatmaps(new List { hidingSet });
+ AddStep("create hidden set", () =>
+ {
+ hidingSet = createTestBeatmapSet(1);
+ hidingSet.Beatmaps[1].Hidden = true;
+
+ hiddenList.Clear();
+ hiddenList.Add(hidingSet);
+ });
+
+ loadBeatmaps(hiddenList);
setSelected(1, 1);
@@ -375,9 +380,14 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestSelectingFilteredRuleset()
{
- var testMixed = createTestBeatmapSet(set_count + 1);
+ BeatmapSetInfo testMixed = null;
+
+ createCarousel();
+
AddStep("add mixed ruleset beatmapset", () =>
{
+ testMixed = createTestBeatmapSet(set_count + 1);
+
for (int i = 0; i <= 2; i++)
{
testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
@@ -429,6 +439,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private void loadBeatmaps(List beatmapSets = null)
{
+ createCarousel();
+
if (beatmapSets == null)
{
beatmapSets = new List();
@@ -448,6 +460,20 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Wait for load", () => changed);
}
+ private void createCarousel(Container target = null)
+ {
+ AddStep("Create carousel", () =>
+ {
+ selectedSets.Clear();
+ eagerSelectedIDs.Clear();
+
+ (target ?? this).Child = carousel = new TestBeatmapCarousel
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ });
+ }
+
private void ensureRandomFetchSuccess() =>
AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet);
@@ -467,8 +493,10 @@ namespace osu.Game.Tests.Visual.SongSelect
private void advanceSelection(bool diff, int direction = 1, int count = 1)
{
if (count == 1)
+ {
AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff));
+ }
else
{
AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index efe7fee5e4..a4b8d1a24a 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -57,23 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelect
typeof(DrawableCarouselBeatmapSet),
};
- private class TestSongSelect : PlaySongSelect
- {
- public Action StartRequested;
-
- public new Bindable Ruleset => base.Ruleset;
-
- public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
- public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
- public new BeatmapCarousel Carousel => base.Carousel;
-
- protected override bool OnStart()
- {
- StartRequested?.Invoke();
- return base.OnStart();
- }
- }
-
private TestSongSelect songSelect;
[BackgroundDependencyLoader]
@@ -101,6 +84,17 @@ namespace osu.Game.Tests.Visual.SongSelect
manager?.Delete(manager.GetAllUsableBeatmapSets());
});
+ [Test]
+ public void TestSingleFilterOnEnter()
+ {
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+
+ createSongSelect();
+
+ AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
+ }
+
[Test]
public void TestAudioResuming()
{
@@ -132,11 +126,13 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(1);
if (rulesetsInSameBeatmap)
+ {
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait();
});
+ }
else
{
addRulesetImportStep(1);
@@ -240,6 +236,22 @@ namespace osu.Game.Tests.Visual.SongSelect
void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++;
}
+ [Test]
+ public void TestModsRetainedBetweenSongSelect()
+ {
+ AddAssert("empty mods", () => !Mods.Value.Any());
+
+ createSongSelect();
+
+ addRulesetImportStep(0);
+
+ changeMods(new OsuModHardRock());
+
+ createSongSelect();
+
+ AddAssert("mods retained", () => Mods.Value.Any());
+ }
+
[Test]
public void TestStartAfterUnMatchingFilterDoesNotStart()
{
@@ -355,5 +367,30 @@ namespace osu.Game.Tests.Visual.SongSelect
base.Dispose(isDisposing);
rulesets?.Dispose();
}
+
+ private class TestSongSelect : PlaySongSelect
+ {
+ public Action StartRequested;
+
+ public new Bindable Ruleset => base.Ruleset;
+
+ public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
+ public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
+ public new BeatmapCarousel Carousel => base.Carousel;
+
+ protected override bool OnStart()
+ {
+ StartRequested?.Invoke();
+ return base.OnStart();
+ }
+
+ public int FilterCount;
+
+ protected override void ApplyFilterToCarousel(FilterCriteria criteria)
+ {
+ FilterCount++;
+ base.ApplyFilterToCarousel(criteria);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
index fcc3a3596f..e495b2a95a 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
private IReadOnlyList requiredGameDependencies => new[]
{
typeof(OsuGame),
- typeof(RavenLogger),
+ typeof(SentryLogger),
typeof(OsuLogo),
typeof(IdleTracker),
typeof(OnScreenDisplay),
@@ -109,16 +109,20 @@ namespace osu.Game.Tests.Visual
AddAssert("check OsuGame DI members", () =>
{
foreach (var type in requiredGameDependencies)
+ {
if (game.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached");
+ }
return true;
});
AddAssert("check OsuGameBase DI members", () =>
{
foreach (var type in requiredGameBaseDependencies)
+ {
if (gameBase.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached");
+ }
return true;
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
new file mode 100644
index 0000000000..2ada5b927b
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
@@ -0,0 +1,145 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneStatefulMenuItem : ManualInputManagerTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuMenu),
+ typeof(StatefulMenuItem),
+ typeof(TernaryStateMenuItem),
+ typeof(DrawableStatefulMenuItem),
+ };
+
+ [Test]
+ public void TestTernaryMenuItem()
+ {
+ OsuMenu menu = null;
+
+ Bindable state = new Bindable(TernaryState.Indeterminate);
+
+ AddStep("create menu", () =>
+ {
+ state.Value = TernaryState.Indeterminate;
+
+ Child = menu = new OsuMenu(Direction.Vertical, true)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Items = new[]
+ {
+ new TernaryStateMenuItem("First"),
+ new TernaryStateMenuItem("Second") { State = { BindTarget = state } },
+ new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } },
+ }
+ };
+ });
+
+ checkState(TernaryState.Indeterminate);
+
+ click();
+ checkState(TernaryState.True);
+
+ click();
+ checkState(TernaryState.False);
+
+ click();
+ checkState(TernaryState.True);
+
+ click();
+ checkState(TernaryState.False);
+
+ AddStep("change state via bindable", () => state.Value = TernaryState.True);
+
+ void click() =>
+ AddStep("click", () =>
+ {
+ InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ void checkState(TernaryState expected)
+ => AddAssert($"state is {expected}", () => state.Value == expected);
+ }
+
+ [Test]
+ public void TestCustomState()
+ {
+ AddStep("create menu", () =>
+ {
+ Child = new OsuMenu(Direction.Vertical, true)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Items = new[]
+ {
+ new TestMenuItem("First", MenuItemType.Standard, getNextState),
+ new TestMenuItem("Second", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State2 } },
+ new TestMenuItem("Third", MenuItemType.Standard, getNextState) { State = { Value = TestStates.State3 } },
+ }
+ };
+ });
+ }
+
+ private TestStates getNextState(TestStates state)
+ {
+ switch (state)
+ {
+ case TestStates.State1:
+ return TestStates.State2;
+
+ case TestStates.State2:
+ return TestStates.State3;
+
+ case TestStates.State3:
+ return TestStates.State1;
+ }
+
+ return TestStates.State1;
+ }
+
+ private class TestMenuItem : StatefulMenuItem
+ {
+ public TestMenuItem(string text, MenuItemType type, Func changeStateFunc)
+ : base(text, changeStateFunc, type)
+ {
+ }
+
+ public override IconUsage? GetIconForState(TestStates state)
+ {
+ switch (state)
+ {
+ case TestStates.State1:
+ return FontAwesome.Solid.DiceOne;
+
+ case TestStates.State2:
+ return FontAwesome.Solid.DiceTwo;
+
+ case TestStates.State3:
+ return FontAwesome.Solid.DiceThree;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(state), state, null);
+ }
+ }
+ }
+
+ private enum TestStates
+ {
+ State1,
+ State2,
+ State3
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs
new file mode 100644
index 0000000000..2abda56a28
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToggleMenuItem.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneToggleMenuItem : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuMenu),
+ typeof(ToggleMenuItem),
+ typeof(DrawableStatefulMenuItem)
+ };
+
+ public TestSceneToggleMenuItem()
+ {
+ Add(new OsuMenu(Direction.Vertical, true)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Items = new[]
+ {
+ new ToggleMenuItem("First"),
+ new ToggleMenuItem("Second") { State = { Value = true } }
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index aa29fc802c..c5998c9cfc 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
index 41d32d9448..9905e17824 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
@@ -102,7 +102,8 @@ namespace osu.Game.Tournament.Tests.Components
Content = "Okay okay, calm down guys. Let's do this!"
}));
- AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId())
+ AddStep("multiple messages", () => testChannel.AddNewMessages(
+ new Message(nextMessageId())
{
Sender = admin,
Content = "I spam you!"
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs
index 3d340e393c..e36b594ff2 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs
@@ -3,7 +3,6 @@
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.TeamIntro;
@@ -13,7 +12,7 @@ namespace osu.Game.Tournament.Tests.Screens
public class TestSceneTeamIntroScreen : LadderTestScene
{
[Cached]
- private readonly Bindable currentMatch = new Bindable();
+ private readonly LadderInfo ladder = new LadderInfo();
[BackgroundDependencyLoader]
private void load()
@@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens
match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA");
match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN");
match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
- currentMatch.Value = match;
+ ladder.CurrentMatch.Value = match;
Add(new TeamIntroScreen
{
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs
index 6f5e17a36e..5cb35a506f 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs
@@ -3,7 +3,6 @@
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.TeamWin;
@@ -13,7 +12,7 @@ namespace osu.Game.Tournament.Tests.Screens
public class TestSceneTeamWinScreen : LadderTestScene
{
[Cached]
- private readonly Bindable currentMatch = new Bindable();
+ private readonly LadderInfo ladder = new LadderInfo();
[BackgroundDependencyLoader]
private void load()
@@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens
match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA");
match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN");
match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
- currentMatch.Value = match;
+ ladder.CurrentMatch.Value = match;
Add(new TeamWinScreen
{
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 371ffcdf9e..d58a724c27 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
index 7005c068ae..8a46da9565 100644
--- a/osu.Game.Tournament/Components/SongBar.cs
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,6 +14,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
using osuTK;
using osuTK.Graphics;
@@ -23,6 +25,9 @@ namespace osu.Game.Tournament.Components
{
private BeatmapInfo beatmap;
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
public BeatmapInfo Beatmap
{
get => beatmap;
@@ -106,6 +111,7 @@ namespace osu.Game.Tournament.Components
Width = main_width,
Height = TournamentBeatmapPanel.HEIGHT,
CornerRadius = TournamentBeatmapPanel.HEIGHT / 2,
+ CornerExponent = 2,
Children = new Drawable[]
{
new Box
@@ -126,6 +132,7 @@ namespace osu.Game.Tournament.Components
{
Masking = true,
CornerRadius = TournamentBeatmapPanel.HEIGHT / 2,
+ CornerExponent = 2,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
@@ -163,7 +170,8 @@ namespace osu.Game.Tournament.Components
string hardRockExtra = "";
string srExtra = "";
- //var ar = beatmap.BaseDifficulty.ApproachRate;
+ var ar = beatmap.BaseDifficulty.ApproachRate;
+
if ((mods & LegacyMods.HardRock) > 0)
{
hardRockExtra = "*";
@@ -172,12 +180,46 @@ namespace osu.Game.Tournament.Components
if ((mods & LegacyMods.DoubleTime) > 0)
{
- //ar *= 1.5f;
+ // temporary local calculation (taken from OsuDifficultyCalculator)
+ double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5;
+ ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
+
bpm *= 1.5f;
length /= 1.5f;
srExtra = "*";
}
+ (string heading, string content)[] stats;
+
+ switch (ruleset.Value.ID)
+ {
+ default:
+ stats = new (string heading, string content)[]
+ {
+ ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
+ ("AR", $"{ar:0.#}{hardRockExtra}"),
+ ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
+ };
+ break;
+
+ case 1:
+ case 3:
+ stats = new (string heading, string content)[]
+ {
+ ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
+ ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
+ };
+ break;
+
+ case 2:
+ stats = new (string heading, string content)[]
+ {
+ ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
+ ("AR", $"{ar:0.#}"),
+ };
+ break;
+ }
+
panelContents.Children = new Drawable[]
{
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss")))
@@ -190,12 +232,7 @@ namespace osu.Game.Tournament.Components
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopLeft
},
- new DiffPiece(
- //("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
- //("AR", $"{ar:0.#}{srExtra}"),
- ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
- ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
- )
+ new DiffPiece(stats)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.BottomRight
@@ -222,7 +259,7 @@ namespace osu.Game.Tournament.Components
Margin = new MarginPadding { Horizontal = 15, Vertical = 1 };
AutoSizeAxes = Axes.Both;
- void cp(SpriteText s, Color4 colour)
+ static void cp(SpriteText s, Color4 colour)
{
s.Colour = colour;
s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15);
@@ -230,7 +267,7 @@ namespace osu.Game.Tournament.Components
for (var i = 0; i < tuples.Length; i++)
{
- var tuple = tuples[i];
+ var (heading, content) = tuples[i];
if (i > 0)
{
@@ -241,9 +278,9 @@ namespace osu.Game.Tournament.Components
});
}
- AddText(new OsuSpriteText { Text = tuple.heading }, s => cp(s, OsuColour.Gray(0.33f)));
+ AddText(new OsuSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f)));
AddText(" ", s => cp(s, OsuColour.Gray(0.33f)));
- AddText(new OsuSpriteText { Text = tuple.content }, s => cp(s, OsuColour.Gray(0.5f)));
+ AddText(new OsuSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f)));
}
}
}
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index f6c1be0e36..51483a0964 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -52,6 +52,7 @@ namespace osu.Game.Tournament.Components
currentMatch.BindTo(ladder.CurrentMatch);
CornerRadius = HEIGHT / 2;
+ CornerExponent = 2;
Masking = true;
AddRangeInternal(new Drawable[]
@@ -131,6 +132,7 @@ namespace osu.Game.Tournament.Components
});
if (!string.IsNullOrEmpty(mods))
+ {
AddInternal(new Sprite
{
Texture = textures.Get($"mods/{mods}"),
@@ -139,6 +141,7 @@ namespace osu.Game.Tournament.Components
Margin = new MarginPadding(20),
Scale = new Vector2(0.5f)
});
+ }
}
private void matchChanged(ValueChangedEvent match)
diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs
index 4f4660f645..206689ca1a 100644
--- a/osu.Game.Tournament/Components/TourneyVideo.cs
+++ b/osu.Game.Tournament/Components/TourneyVideo.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Video;
+using osu.Framework.Timing;
using osu.Game.Graphics;
namespace osu.Game.Tournament.Components
@@ -15,6 +16,8 @@ namespace osu.Game.Tournament.Components
{
private readonly VideoSprite video;
+ private readonly ManualClock manualClock;
+
public TourneyVideo(Stream stream)
{
if (stream == null)
@@ -26,11 +29,14 @@ namespace osu.Game.Tournament.Components
};
}
else
+ {
InternalChild = video = new VideoSprite(stream)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
+ Clock = new FramedClock(manualClock = new ManualClock())
};
+ }
}
public bool Loop
@@ -41,5 +47,17 @@ namespace osu.Game.Tournament.Components
video.Loop = value;
}
}
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (manualClock != null && Clock.ElapsedFrameTime < 100)
+ {
+ // we want to avoid seeking as much as possible, because we care about performance, not sync.
+ // to avoid seeking completely, we only increment out local clock when in an updating state.
+ manualClock.CurrentTime += Clock.ElapsedFrameTime;
+ }
+ }
}
}
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index e05d96e098..b19f2bedf0 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -60,6 +60,7 @@ namespace osu.Game.Tournament.IPC
const string file_ipc_channel_filename = "ipc-channel.txt";
if (Storage.Exists(file_ipc_filename))
+ {
scheduled = Scheduler.AddDelayed(delegate
{
try
@@ -134,6 +135,7 @@ namespace osu.Game.Tournament.IPC
// file might be in use.
}
}, 250, true);
+ }
}
catch (Exception e)
{
@@ -150,7 +152,7 @@ namespace osu.Game.Tournament.IPC
{
protected override string LocateBasePath()
{
- bool checkExists(string p)
+ static bool checkExists(string p)
{
return File.Exists(Path.Combine(p, "ipc.txt"));
}
@@ -178,7 +180,7 @@ namespace osu.Game.Tournament.IPC
try
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
+ stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath))
return stableInstallPath;
diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs
index 547c4eab08..5db0b01547 100644
--- a/osu.Game.Tournament/Models/LadderInfo.cs
+++ b/osu.Game.Tournament/Models/LadderInfo.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Bindables;
+using osu.Game.Rulesets;
namespace osu.Game.Tournament.Models
{
@@ -14,6 +15,8 @@ namespace osu.Game.Tournament.Models
[Serializable]
public class LadderInfo
{
+ public Bindable Ruleset = new Bindable();
+
public BindableList Matches = new BindableList();
public BindableList Rounds = new BindableList();
public BindableList Teams = new BindableList();
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
index 8a66ca7bf6..b9a19090df 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
groups.Add(g);
nextGroupName++;
- if (i < (int)Math.Ceiling(numGroups / 2f))
+ if (i < (int)MathF.Ceiling(numGroups / 2f))
topGroups.Add(g);
else
bottomGroups.Add(g);
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
index b147d680f0..a345f93896 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
@@ -125,9 +125,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
foreach (var c in Children)
{
- var stc = c as ScrollingTeam;
-
- if (stc == null)
+ if (!(c is ScrollingTeam stc))
continue;
if (closest == null)
@@ -203,15 +201,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
foreach (var c in Children)
{
- ScrollingTeam st = c as ScrollingTeam;
-
- if (st == null)
- continue;
-
- if (st.Team == team)
+ if (c is ScrollingTeam st)
{
- st.FadeOut(200);
- st.Expire();
+ if (st.Team == team)
+ {
+ st.FadeOut(200);
+ st.Expire();
+ }
}
}
}
@@ -295,14 +291,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
{
foreach (var c in Children)
{
- ScrollingTeam st = c as ScrollingTeam;
- if (st == null)
- continue;
-
- if (st.Selected)
+ if (c is ScrollingTeam st)
{
- st.Selected = false;
- RemoveTeam(st.Team);
+ if (st.Selected)
+ {
+ st.Selected = false;
+ RemoveTeam(st.Team);
+ }
}
}
}
diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
index 3a14b6d9c2..5efa0a1e69 100644
--- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
+++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
@@ -15,7 +15,6 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Drawings.Components;
@@ -24,7 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Tournament.Screens.Drawings
{
- public class DrawingsScreen : CompositeDrawable
+ public class DrawingsScreen : TournamentScreen
{
private const string results_filename = "drawings_results.txt";
@@ -128,21 +127,21 @@ namespace osu.Game.Tournament.Screens.Drawings
// Control panel container
new ControlPanel
{
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Begin random",
Action = teamsContainer.StartScrolling,
},
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Stop random",
Action = teamsContainer.StopScrolling,
},
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
@@ -150,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Drawings
Action = reloadTeams
},
new ControlPanel.Spacer(),
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
@@ -195,7 +194,7 @@ namespace osu.Game.Tournament.Screens.Drawings
}
}
- writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run((Action)writeAction);
+ writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run(writeAction);
}
private void reloadTeams()
diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
index b036350879..7119533743 100644
--- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -225,9 +225,7 @@ namespace osu.Game.Tournament.Screens.Editors
beatmapId.Value = Model.ID.ToString();
beatmapId.BindValueChanged(idString =>
{
- int parsed;
-
- int.TryParse(idString.NewValue, out parsed);
+ int.TryParse(idString.NewValue, out var parsed);
Model.ID = parsed;
@@ -266,12 +264,14 @@ namespace osu.Game.Tournament.Screens.Editors
drawableContainer.Clear();
if (Model.BeatmapInfo != null)
+ {
drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 300
};
+ }
}
}
}
diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
index a4479f3cfd..494dd73edd 100644
--- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
@@ -11,9 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
@@ -25,14 +23,14 @@ namespace osu.Game.Tournament.Screens.Editors
public class TeamEditorScreen : TournamentEditorScreen
{
[Resolved]
- private Framework.Game game { get; set; }
+ private TournamentGameBase game { get; set; }
protected override BindableList Storage => LadderInfo.Teams;
[BackgroundDependencyLoader]
private void load()
{
- ControlPanel.Add(new OsuButton
+ ControlPanel.Add(new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Add all countries",
@@ -199,6 +197,9 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
protected IAPIProvider API { get; private set; }
+ [Resolved]
+ private TournamentGameBase game { get; set; }
+
private readonly Bindable userId = new Bindable();
private readonly Container drawableContainer;
@@ -266,9 +267,7 @@ namespace osu.Game.Tournament.Screens.Editors
userId.Value = user.Id.ToString();
userId.BindValueChanged(idString =>
{
- long parsed;
-
- long.TryParse(idString.NewValue, out parsed);
+ long.TryParse(idString.NewValue, out var parsed);
user.Id = parsed;
@@ -281,25 +280,7 @@ namespace osu.Game.Tournament.Screens.Editors
return;
}
- var req = new GetUserRequest(user.Id);
-
- req.Success += res =>
- {
- // TODO: this should be done in a better way.
- user.Username = res.Username;
- user.Country = res.Country;
- user.Cover = res.Cover;
-
- updatePanel();
- };
-
- req.Failure += _ =>
- {
- user.Id = 1;
- updatePanel();
- };
-
- API.Queue(req);
+ game.PopulateUser(user, updatePanel, updatePanel);
}, true);
}
diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
index 50d3207345..32cf6bbcc8 100644
--- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osuTK;
@@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Screens.Editors
{
Children = new Drawable[]
{
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Add new",
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
index 78455c8bb7..cc7903f2fa 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
var diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value);
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
- winningBar.ResizeWidthTo(Math.Min(0.4f, (float)Math.Pow(diff / 1500000f, 0.5) / 2), 400, Easing.OutQuint);
+ winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
}
protected override void Update()
diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
index b9a74bfe16..6a3095d42d 100644
--- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
@@ -103,13 +103,13 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
Children = new Drawable[]
{
- warmupButton = new OsuButton
+ warmupButton = new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle warmup",
Action = () => warmup.Toggle()
},
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle chat",
diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs
index 34e0dc770f..84a329085a 100644
--- a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs
+++ b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
{
base.LoadComplete();
- Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2);
+ static Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2);
var q1 = Source.ScreenSpaceDrawQuad;
var q2 = Destination.ScreenSpaceDrawQuad;
diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs
index f613ce5f46..0c450a66b4 100644
--- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs
+++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
@@ -32,9 +33,9 @@ namespace osu.Game.Tournament.Screens.Ladder
protected override bool OnScroll(ScrollEvent e)
{
- var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale);
+ var newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale);
- this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint);
+ this.MoveTo(target -= e.MousePosition * (newScale - scale), 2000, Easing.OutQuint);
this.ScaleTo(scale = newScale, 2000, Easing.OutQuint);
return true;
diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
index 83a41a662f..66e68a0f37 100644
--- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
+++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
@@ -81,8 +81,10 @@ namespace osu.Game.Tournament.Screens.Ladder
LadderInfo.Matches.ItemsRemoved += matches =>
{
foreach (var p in matches)
- foreach (var d in MatchesContainer.Where(d => d.Match == p))
- d.Expire();
+ {
+ foreach (var d in MatchesContainer.Where(d => d.Match == p))
+ d.Expire();
+ }
layout.Invalidate();
};
diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index ec55bb5b54..c3875716b8 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -60,32 +60,32 @@ namespace osu.Game.Tournament.Screens.MapPool
{
Text = "Current Mode"
},
- buttonRedBan = new OsuButton
+ buttonRedBan = new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Red Ban",
Action = () => setMode(TeamColour.Red, ChoiceType.Ban)
},
- buttonBlueBan = new OsuButton
+ buttonBlueBan = new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Blue Ban",
Action = () => setMode(TeamColour.Blue, ChoiceType.Ban)
},
- buttonRedPick = new OsuButton
+ buttonRedPick = new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Red Pick",
Action = () => setMode(TeamColour.Red, ChoiceType.Pick)
},
- buttonBluePick = new OsuButton
+ buttonBluePick = new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Blue Pick",
Action = () => setMode(TeamColour.Blue, ChoiceType.Pick)
},
new ControlPanel.Spacer(),
- new OsuButton
+ new TourneyButton
{
RelativeSizeAxes = Axes.X,
Text = "Reset",
@@ -120,7 +120,7 @@ namespace osu.Game.Tournament.Screens.MapPool
pickColour = colour;
pickType = choiceType;
- Color4 setColour(bool active) => active ? Color4.White : Color4.Gray;
+ static Color4 setColour(bool active) => active ? Color4.White : Color4.Gray;
buttonRedBan.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Ban);
buttonBlueBan.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Ban);
diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs
index a67daa2756..8e1481d87c 100644
--- a/osu.Game.Tournament/Screens/SetupScreen.cs
+++ b/osu.Game.Tournament/Screens/SetupScreen.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -10,6 +11,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osu.Game.Tournament.IPC;
using osuTK;
using osuTK.Graphics;
@@ -28,6 +30,9 @@ namespace osu.Game.Tournament.Screens
[Resolved]
private IAPIProvider api { get; set; }
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -85,7 +90,34 @@ namespace osu.Game.Tournament.Screens
Value = api?.LocalUser.Value.Username,
Failing = api?.IsLoggedIn != true,
Description = "In order to access the API and display metadata, a login is required."
- }
+ },
+ new LabelledDropdown
+ {
+ Label = "Ruleset",
+ Description = "Decides what stats are displayed and which ranks are retrieved for players",
+ Items = rulesets.AvailableRulesets,
+ Current = LadderInfo.Ruleset,
+ },
+ };
+ }
+
+ public class LabelledDropdown : LabelledComponent, T>
+ {
+ public LabelledDropdown()
+ : base(true)
+ {
+ }
+
+ public IEnumerable Items
+ {
+ get => Component.Items;
+ set => Component.Items = value;
+ }
+
+ protected override OsuDropdown CreateComponent() => new OsuDropdown
+ {
+ RelativeSizeAxes = Axes.X,
+ Width = 0.5f,
};
}
diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs
index c901a5c7ef..47c923ff30 100644
--- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs
+++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs
@@ -164,6 +164,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
if (team != null)
{
foreach (var p in team.Players)
+ {
players.Add(new OsuSpriteText
{
Text = p.Username,
@@ -172,6 +173,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft,
Origin = left ? Anchor.CentreRight : Anchor.CentreLeft,
});
+ }
}
}
diff --git a/osu.Game.Tournament/Screens/TournamentScreen.cs b/osu.Game.Tournament/Screens/TournamentScreen.cs
index 9d58ca2240..0b5b3e728b 100644
--- a/osu.Game.Tournament/Screens/TournamentScreen.cs
+++ b/osu.Game.Tournament/Screens/TournamentScreen.cs
@@ -10,6 +10,8 @@ namespace osu.Game.Tournament.Screens
{
public abstract class TournamentScreen : CompositeDrawable
{
+ public const double FADE_DELAY = 200;
+
[Resolved]
protected LadderInfo LadderInfo { get; private set; }
@@ -18,14 +20,8 @@ namespace osu.Game.Tournament.Screens
RelativeSizeAxes = Axes.Both;
}
- public override void Hide()
- {
- this.FadeOut(200);
- }
+ public override void Hide() => this.FadeOut(FADE_DELAY);
- public override void Show()
- {
- this.FadeIn(200);
- }
+ public override void Show() => this.FadeIn(FADE_DELAY);
}
}
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index dbfa70704b..b5e4a2756a 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.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.Drawing;
using System.IO;
using System.Linq;
@@ -11,22 +12,23 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
+using osu.Game.Users;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tournament
{
+ [Cached(typeof(TournamentGameBase))]
public abstract class TournamentGameBase : OsuGameBase
{
private const string bracket_filename = "bracket.json";
@@ -76,7 +78,7 @@ namespace osu.Game.Tournament
AddRange(new[]
{
- new OsuButton
+ new TourneyButton
{
Text = "Save Changes",
Width = 140,
@@ -102,7 +104,7 @@ namespace osu.Game.Tournament
Colour = Color4.Red,
RelativeSizeAxes = Axes.Both,
},
- new SpriteText
+ new OsuSpriteText
{
Text = "Please make the window wider",
Font = OsuFont.Default.With(weight: "bold"),
@@ -127,6 +129,8 @@ namespace osu.Game.Tournament
ladder = new LadderInfo();
}
+ Ruleset.BindTo(ladder.Ruleset);
+
dependencies.Cache(ladder);
bool addedInfo = false;
@@ -165,15 +169,17 @@ namespace osu.Game.Tournament
// link matches to rounds
foreach (var round in ladder.Rounds)
- foreach (var id in round.Matches)
{
- var found = ladder.Matches.FirstOrDefault(p => p.ID == id);
-
- if (found != null)
+ foreach (var id in round.Matches)
{
- found.Round.Value = round;
- if (round.StartDate.Value > found.Date.Value)
- found.Date.Value = round.StartDate.Value;
+ var found = ladder.Matches.FirstOrDefault(p => p.ID == id);
+
+ if (found != null)
+ {
+ found.Round.Value = round;
+ if (round.StartDate.Value > found.Date.Value)
+ found.Date.Value = round.StartDate.Value;
+ }
}
}
@@ -193,15 +199,14 @@ namespace osu.Game.Tournament
bool addedInfo = false;
foreach (var t in ladder.Teams)
- foreach (var p in t.Players)
- if (string.IsNullOrEmpty(p.Username))
+ {
+ foreach (var p in t.Players)
{
- var req = new GetUserRequest(p.Id);
- req.Perform(API);
- p.Username = req.Result.Username;
-
+ if (p.Username == null || p.Statistics == null)
+ PopulateUser(p);
addedInfo = true;
}
+ }
return addedInfo;
}
@@ -214,19 +219,46 @@ namespace osu.Game.Tournament
bool addedInfo = false;
foreach (var r in ladder.Rounds)
- foreach (var b in r.Beatmaps)
- if (b.BeatmapInfo == null)
+ {
+ foreach (var b in r.Beatmaps)
{
- var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
- req.Perform(API);
- b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
+ if (b.BeatmapInfo == null && b.ID > 0)
+ {
+ var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
+ req.Perform(API);
+ b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
- addedInfo = true;
+ addedInfo = true;
+ }
}
+ }
return addedInfo;
}
+ public void PopulateUser(User user, Action success = null, Action failure = null)
+ {
+ var req = new GetUserRequest(user.Id, Ruleset.Value);
+
+ req.Success += res =>
+ {
+ user.Username = res.Username;
+ user.Statistics = res.Statistics;
+ user.Country = res.Country;
+ user.Cover = res.Cover;
+
+ success?.Invoke();
+ };
+
+ req.Failure += _ =>
+ {
+ user.Id = 1;
+ failure?.Invoke();
+ };
+
+ API.Queue(req);
+ }
+
protected override void LoadComplete()
{
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs
index 02ee1c8603..de3d685c31 100644
--- a/osu.Game.Tournament/TournamentSceneManager.cs
+++ b/osu.Game.Tournament/TournamentSceneManager.cs
@@ -8,7 +8,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
-using osu.Game.Graphics.UserInterface;
+using osu.Framework.Threading;
+using osu.Game.Graphics;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens;
@@ -36,6 +37,7 @@ namespace osu.Game.Tournament
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay();
private Container chatContainer;
+ private FillFlowContainer buttons;
public TournamentSceneManager()
{
@@ -101,68 +103,136 @@ namespace osu.Game.Tournament
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
- new FillFlowContainer
+ buttons = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
+ Spacing = new Vector2(2),
+ Padding = new MarginPadding(2),
Children = new Drawable[]
{
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) },
- new Container { RelativeSizeAxes = Axes.X, Height = 50 },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) },
- new Container { RelativeSizeAxes = Axes.X, Height = 50 },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) },
- new Container { RelativeSizeAxes = Axes.X, Height = 50 },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) },
- new Container { RelativeSizeAxes = Axes.X, Height = 50 },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) },
- new Container { RelativeSizeAxes = Axes.X, Height = 50 },
- new OsuButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) },
+ new ScreenButton(typeof(SetupScreen)) { Text = "Setup", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(TeamEditorScreen)) { Text = "Team Editor", RequestSelection = SetScreen },
+ new ScreenButton(typeof(RoundEditorScreen)) { Text = "Rounds Editor", RequestSelection = SetScreen },
+ new ScreenButton(typeof(LadderEditorScreen)) { Text = "Bracket Editor", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(ScheduleScreen)) { Text = "Schedule", RequestSelection = SetScreen },
+ new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen },
+ new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(TeamWinScreen)) { Text = "Win", RequestSelection = SetScreen },
+ new Separator(),
+ new ScreenButton(typeof(DrawingsScreen)) { Text = "Drawings", RequestSelection = SetScreen },
+ new ScreenButton(typeof(ShowcaseScreen)) { Text = "Showcase", RequestSelection = SetScreen },
}
},
},
},
};
+ foreach (var drawable in screens)
+ drawable.Hide();
+
SetScreen(typeof(SetupScreen));
}
+ private float depth;
+
+ private Drawable currentScreen;
+ private ScheduledDelegate scheduledHide;
+
public void SetScreen(Type screenType)
{
- var screen = screens.FirstOrDefault(s => s.GetType() == screenType);
- if (screen == null) return;
+ var target = screens.FirstOrDefault(s => s.GetType() == screenType);
- foreach (var s in screens.Children)
+ if (target == null || currentScreen == target) return;
+
+ if (scheduledHide?.Completed == false)
{
- if (s == screen)
- {
- s.Show();
- if (s is IProvideVideo)
- video.FadeOut(200);
- else
- video.Show();
- }
- else
- s.Hide();
+ scheduledHide.RunTask();
+ scheduledHide.Cancel(); // see https://github.com/ppy/osu-framework/issues/2967
+ scheduledHide = null;
}
- switch (screen)
+ var lastScreen = currentScreen;
+ currentScreen = target;
+
+ if (currentScreen is IProvideVideo)
+ {
+ video.FadeOut(200);
+
+ // delay the hide to avoid a double-fade transition.
+ scheduledHide = Scheduler.AddDelayed(() => lastScreen?.Hide(), TournamentScreen.FADE_DELAY);
+ }
+ else
+ {
+ lastScreen?.Hide();
+ video.Show();
+ }
+
+ screens.ChangeChildDepth(currentScreen, depth--);
+ currentScreen.Show();
+
+ switch (currentScreen)
{
case GameplayScreen _:
case MapPoolScreen _:
- chatContainer.FadeIn(100);
+ chatContainer.FadeIn(TournamentScreen.FADE_DELAY);
break;
default:
- chatContainer.FadeOut(100);
+ chatContainer.FadeOut(TournamentScreen.FADE_DELAY);
break;
}
+
+ foreach (var s in buttons.OfType())
+ s.IsSelected = screenType == s.Type;
+ }
+
+ private class Separator : CompositeDrawable
+ {
+ public Separator()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 20;
+ }
+ }
+
+ private class ScreenButton : TourneyButton
+ {
+ public readonly Type Type;
+
+ public ScreenButton(Type type)
+ {
+ Type = type;
+ BackgroundColour = OsuColour.Gray(0.2f);
+ Action = () => RequestSelection(type);
+
+ RelativeSizeAxes = Axes.X;
+ }
+
+ private bool isSelected;
+
+ public Action RequestSelection;
+
+ public bool IsSelected
+ {
+ get => isSelected;
+ set
+ {
+ if (value == isSelected)
+ return;
+
+ isSelected = value;
+ BackgroundColour = isSelected ? Color4.SkyBlue : OsuColour.Gray(0.2f);
+ SpriteText.Colour = isSelected ? Color4.Black : Color4.White;
+ }
+ }
}
}
}
diff --git a/osu.Game.Tournament/TourneyButton.cs b/osu.Game.Tournament/TourneyButton.cs
new file mode 100644
index 0000000000..12872d3197
--- /dev/null
+++ b/osu.Game.Tournament/TourneyButton.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Tournament
+{
+ public class TourneyButton : OsuButton
+ {
+ public TourneyButton()
+ : base(null)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj
index bddaff0a80..8e881fdd9c 100644
--- a/osu.Game.Tournament/osu.Game.Tournament.csproj
+++ b/osu.Game.Tournament/osu.Game.Tournament.csproj
@@ -1,9 +1,7 @@
-
- netstandard2.0
+ netstandard2.1
Library
- AnyCPU
true
tools for tournaments.
diff --git a/osu.Game.props b/osu.Game.props
deleted file mode 100644
index 1a3c0aec3e..0000000000
--- a/osu.Game.props
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- 7.2
-
-
- ..\app.manifest
-
-
-
- osu.licenseheader
-
-
-
-
-
-
- ppy Pty Ltd
- Copyright (c) 2019 ppy Pty Ltd
-
- NU1701
-
-
\ No newline at end of file
diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs
index 22ce7d4711..5df656e1e0 100644
--- a/osu.Game/Audio/PreviewTrack.cs
+++ b/osu.Game/Audio/PreviewTrack.cs
@@ -9,48 +9,52 @@ using osu.Framework.Threading;
namespace osu.Game.Audio
{
+ [LongRunningLoad]
public abstract class PreviewTrack : Component
{
///
/// Invoked when this has stopped playing.
+ /// Not invoked in a thread-safe context.
///
public event Action Stopped;
///
/// Invoked when this has started playing.
+ /// Not invoked in a thread-safe context.
///
public event Action Started;
- private Track track;
+ protected Track Track { get; private set; }
+
private bool hasStarted;
[BackgroundDependencyLoader]
private void load()
{
- track = GetTrack();
- if (track != null)
- track.Completed += () => Schedule(Stop);
+ Track = GetTrack();
+ if (Track != null)
+ Track.Completed += Stop;
}
///
/// Length of the track.
///
- public double Length => track?.Length ?? 0;
+ public double Length => Track?.Length ?? 0;
///
/// The current track time.
///
- public double CurrentTime => track?.CurrentTime ?? 0;
+ public double CurrentTime => Track?.CurrentTime ?? 0;
///
/// Whether the track is loaded.
///
- public bool TrackLoaded => track?.IsLoaded ?? false;
+ public bool TrackLoaded => Track?.IsLoaded ?? false;
///
/// Whether the track is playing.
///
- public bool IsRunning => track?.IsRunning ?? false;
+ public bool IsRunning => Track?.IsRunning ?? false;
private ScheduledDelegate startDelegate;
@@ -60,7 +64,7 @@ namespace osu.Game.Audio
/// Whether the track is started or already playing.
public bool Start()
{
- if (track == null)
+ if (Track == null)
return false;
startDelegate = Schedule(() =>
@@ -70,7 +74,7 @@ namespace osu.Game.Audio
hasStarted = true;
- track.Restart();
+ Track.Restart();
Started?.Invoke();
});
@@ -84,7 +88,7 @@ namespace osu.Game.Audio
{
startDelegate?.Cancel();
- if (track == null)
+ if (Track == null)
return;
if (!hasStarted)
@@ -92,7 +96,8 @@ namespace osu.Game.Audio
hasStarted = false;
- track.Stop();
+ Track.Stop();
+
Stopped?.Invoke();
}
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
index e12c46ef16..6f0b62543d 100644
--- a/osu.Game/Audio/PreviewTrackManager.cs
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Audio
private AudioManager audio;
private PreviewTrackStore trackStore;
- private TrackManagerPreviewTrack current;
+ protected TrackManagerPreviewTrack CurrentTrack;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
@@ -46,18 +46,21 @@ namespace osu.Game.Audio
{
var track = CreatePreviewTrack(beatmapSetInfo, trackStore);
- track.Started += () =>
+ track.Started += () => Schedule(() =>
{
- current?.Stop();
- current = track;
+ CurrentTrack?.Stop();
+ CurrentTrack = track;
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable);
- };
+ });
- track.Stopped += () =>
+ track.Stopped += () => Schedule(() =>
{
- current = null;
+ if (CurrentTrack != track)
+ return;
+
+ CurrentTrack = null;
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
- };
+ });
return track;
}
@@ -73,11 +76,11 @@ namespace osu.Game.Audio
/// The which may be the owner of the .
public void StopAnyPlaying(IPreviewTrackOwner source)
{
- if (current == null || current.Owner != source)
+ if (CurrentTrack == null || CurrentTrack.Owner != source)
return;
- current.Stop();
- current = null;
+ CurrentTrack.Stop();
+ // CurrentTrack should not be set to null here as it will result in incorrect handling in the track.Stopped callback above.
}
///
@@ -85,7 +88,7 @@ namespace osu.Game.Audio
///
protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore);
- protected class TrackManagerPreviewTrack : PreviewTrack
+ public class TrackManagerPreviewTrack : PreviewTrack
{
public IPreviewTrackOwner Owner { get; private set; }
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 8727431e0e..c56fec67aa 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -56,10 +56,22 @@ namespace osu.Game.Beatmaps
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
///
/// The difficulty value to be mapped.
- /// The values that define the two linear ranges.
- /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
- /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
- /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
+ /// The values that define the two linear ranges.
+ ///
+ /// -
+ /// od0
+ /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
+ ///
+ /// -
+ /// od5
+ /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
+ ///
+ /// -
+ /// od10
+ /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
+ ///
+ ///
+ ///
/// Value to which the difficulty value maps in the specified range.
public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
=> DifficultyRange(difficulty, range.od0, range.od5, range.od10);
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 198046df4f..6e82c465dc 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps
public string MD5Hash { get; set; }
// General
- public int AudioLeadIn { get; set; }
+ public double AudioLeadIn { get; set; }
public bool Countdown { get; set; } = true;
public float StackLeniency { get; set; } = 0.7f;
public bool SpecialStyle { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 6e485f642a..bc78e50f5d 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -25,7 +25,7 @@ using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
{
@@ -129,9 +129,12 @@ namespace osu.Game.Beatmaps
{
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
+ LogForModel(beatmapSet, "Validating online IDs...");
+
// ensure all IDs are unique
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
{
+ LogForModel(beatmapSet, "Found non-unique IDs, resetting...");
resetIds();
return;
}
@@ -144,8 +147,12 @@ namespace osu.Game.Beatmaps
// reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set.
// we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted.
var existing = CheckForExisting(beatmapSet);
+
if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b)))
+ {
+ LogForModel(beatmapSet, "Found existing import with IDs already, resetting...");
resetIds();
+ }
}
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null);
@@ -296,8 +303,13 @@ namespace osu.Game.Beatmaps
var decoder = Decoder.GetDecoder(sr);
IBeatmap beatmap = decoder.Decode(sr);
+ string hash = ms.ComputeSHA2Hash();
+
+ if (beatmapInfos.Any(b => b.Hash == hash))
+ continue;
+
beatmap.BeatmapInfo.Path = file.Filename;
- beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
+ beatmap.BeatmapInfo.Hash = hash;
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
@@ -322,7 +334,8 @@ namespace osu.Game.Beatmaps
var lastObject = b.HitObjects.Last();
- double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
+ //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
+ double endTime = lastObject.GetEndTime();
double startTime = b.HitObjects.First().StartTime;
return endTime - startTime;
@@ -380,25 +393,30 @@ namespace osu.Game.Beatmaps
var req = new GetBeatmapRequest(beatmap);
- req.Success += res =>
- {
- LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
-
- beatmap.Status = res.Status;
- beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
- beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
- beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
- };
-
- req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); };
+ req.Failure += fail;
try
{
// intentionally blocking to limit web request concurrency
req.Perform(api);
+
+ var res = req.Result;
+
+ beatmap.Status = res.Status;
+ beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
+ beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
+ beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
+
+ LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
}
catch (Exception e)
{
+ fail(e);
+ }
+
+ void fail(Exception e)
+ {
+ beatmap.OnlineBeatmapID = null;
LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})");
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index b879b92f01..4924842e81 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps
{
try
{
- return (trackStore ?? (trackStore = AudioManager.GetTrackStore(store))).Get(getPathForFile(Metadata.AudioFile));
+ return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile));
}
catch
{
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 0861e00d8d..7351187ab9 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints
private ControlPointGroup controlPointGroup;
- public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup;
+ public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index c3e2b469ae..ce2783004c 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.ControlPoints
}
///
- /// Check whether should be added.
+ /// Check whether should be added.
///
/// The time to find the timing control point at.
/// A point to be added.
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index c00b04b660..51b3377394 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -43,6 +43,11 @@ namespace osu.Game.Beatmaps.ControlPoints
set => BeatLengthBindable.Value = value;
}
+ ///
+ /// The BPM at this control point.
+ ///
+ public double BPM => 60000 / BeatLength;
+
public override bool EquivalentTo(ControlPoint other) =>
other is TimingControlPoint otherTyped
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
index d0db7765c2..5245bc319d 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures;
namespace osu.Game.Beatmaps.Drawables
{
+ [LongRunningLoad]
public class BeatmapSetCover : Sprite
{
private readonly BeatmapSetInfo set;
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
index 40c329eb7e..45122f6312 100644
--- a/osu.Game/Beatmaps/Formats/Decoder.cs
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps.Formats
///
/// Registers a fallback decoder instantiation function.
/// The fallback will be returned if the first non-empty line of the decoded stream does not match any known magic.
- /// Calling this method will overwrite any existing global fallback registration for type - use with caution.
+ /// Calling this method will overwrite any existing global fallback registration for type - use with caution.
///
/// Type of object being decoded.
/// A function that constructs the fallback.
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index aeb5df46f8..838b1c2f07 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -293,9 +293,7 @@ namespace osu.Game.Beatmaps.Formats
{
string[] split = line.Split(',');
- EventType type;
-
- if (!Enum.TryParse(split[0], out type))
+ if (!Enum.TryParse(split[0], out EventType type))
throw new InvalidDataException($@"Unknown event type: {split[0]}");
switch (type)
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 5dbd67d304..f94ab3f27b 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -83,9 +83,7 @@ namespace osu.Game.Beatmaps.Formats
{
storyboardSprite = null;
- EventType type;
-
- if (!Enum.TryParse(split[0], out type))
+ if (!Enum.TryParse(split[0], out EventType type))
throw new InvalidDataException($@"Unknown event type: {split[0]}");
switch (type)
@@ -144,16 +142,16 @@ namespace osu.Game.Beatmaps.Formats
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
- }
break;
+ }
case "L":
{
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
var loopCount = int.Parse(split[2]);
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
- }
break;
+ }
default:
{
@@ -171,16 +169,16 @@ namespace osu.Game.Beatmaps.Formats
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
- }
break;
+ }
case "S":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
- }
break;
+ }
case "V":
{
@@ -189,16 +187,16 @@ namespace osu.Game.Beatmaps.Formats
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
- }
break;
+ }
case "R":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
- }
break;
+ }
case "M":
{
@@ -208,24 +206,24 @@ namespace osu.Game.Beatmaps.Formats
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
- }
break;
+ }
case "MX":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
- }
break;
+ }
case "MY":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
- }
break;
+ }
case "C":
{
@@ -238,8 +236,8 @@ namespace osu.Game.Beatmaps.Formats
timelineGroup?.Colour.Add(easing, startTime, endTime,
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
- }
break;
+ }
case "P":
{
@@ -259,14 +257,16 @@ namespace osu.Game.Beatmaps.Formats
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
break;
}
- }
+
break;
+ }
default:
throw new InvalidDataException($@"Unknown command type: {commandType}");
}
- }
+
break;
+ }
}
}
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 3fc33e9f52..44d6d33cef 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -133,8 +133,10 @@ namespace osu.Game.Beatmaps
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
foreach (var mod in mods.OfType())
- foreach (var obj in converted.HitObjects)
- mod.ApplyToHitObject(obj);
+ {
+ foreach (var obj in converted.HitObjects)
+ mod.ApplyToHitObject(obj);
+ }
processor?.PostProcess();
@@ -148,7 +150,7 @@ namespace osu.Game.Beatmaps
public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
- public Task LoadBeatmapAsync() => (beatmapLoadTask ?? (beatmapLoadTask = Task.Factory.StartNew(() =>
+ public Task LoadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap();
@@ -160,7 +162,7 @@ namespace osu.Game.Beatmaps
b.BeatmapInfo = BeatmapInfo;
return b;
- }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)));
+ }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
public IBeatmap Beatmap
{
diff --git a/osu.Game/Configuration/BackgroundSource.cs b/osu.Game/Configuration/BackgroundSource.cs
new file mode 100644
index 0000000000..5726e96eb1
--- /dev/null
+++ b/osu.Game/Configuration/BackgroundSource.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Configuration
+{
+ public enum BackgroundSource
+ {
+ Skin,
+ Beatmap
+ }
+}
diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
index 02382cfd2b..1ef4c2527a 100644
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ b/osu.Game/Configuration/DatabasedConfigManager.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Configuration
protected override void PerformLoad()
{
databasedSettings = settings.Query(ruleset?.ID, variant);
- legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
+ legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _));
}
protected override bool PerformSave()
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index c0ce08ba08..b71463841a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -117,6 +117,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f);
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
+
+ Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
}
public OsuConfigManager(Storage storage)
@@ -186,6 +188,7 @@ namespace osu.Game.Configuration
UIScale,
IntroSequence,
UIHoldActivationDelay,
- HitLighting
+ HitLighting,
+ MenuBackgroundSource
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 9fed8e03ac..7cce2fb92f 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -54,13 +54,13 @@ namespace osu.Game.Database
public Action PostNotification { protected get; set; }
///
- /// Fired when a new becomes available in the database.
+ /// Fired when a new becomes available in the database.
/// This is not guaranteed to run on the update thread.
///
public event Action ItemAdded;
///
- /// Fired when a is removed from the database.
+ /// Fired when a is removed from the database.
/// This is not guaranteed to run on the update thread.
///
public event Action ItemRemoved;
@@ -95,7 +95,7 @@ namespace osu.Game.Database
}
///
- /// Import one or more items from filesystem .
+ /// Import one or more items from filesystem .
/// This will post notifications tracking progress.
///
/// One or more archive locations on disk.
@@ -173,7 +173,7 @@ namespace osu.Game.Database
}
///
- /// Import one from the filesystem and delete the file on success.
+ /// Import one from the filesystem and delete the file on success.
///
/// The archive location on disk.
/// An optional cancellation token.
@@ -264,15 +264,18 @@ namespace osu.Game.Database
{
// for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
+
foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith)))
+ {
using (Stream s = reader.GetStream(file))
s.CopyTo(hashable);
+ }
return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null;
}
///
- /// Import an item from a .
+ /// Import an item from a .
///
/// The model to be imported.
/// An optional archive to use for model population.
@@ -485,12 +488,16 @@ namespace osu.Game.Database
// import files to manager
foreach (string file in reader.Filenames)
+ {
using (Stream s = reader.GetStream(file))
+ {
fileInfos.Add(new TFileModel
{
Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)),
FileInfo = files.Add(s)
});
+ }
+ }
return fileInfos;
}
@@ -582,7 +589,7 @@ namespace osu.Game.Database
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
///
- /// After an existing is found during an import process, the default behaviour is to restore the existing
+ /// After an existing is found during an import process, the default behaviour is to restore the existing
/// item and skip the import. This method allows changing that behaviour.
///
/// The existing model.
@@ -651,8 +658,10 @@ namespace osu.Game.Database
private void handleEvent(Action a)
{
if (delayingEvents)
+ {
lock (queuedEvents)
queuedEvents.Add(a);
+ }
else
a.Invoke();
}
diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs
index a81dff3475..0b7d63f469 100644
--- a/osu.Game/Database/DownloadableArchiveModelManager.cs
+++ b/osu.Game/Database/DownloadableArchiveModelManager.cs
@@ -41,17 +41,17 @@ namespace osu.Game.Database
}
///
- /// Creates the download request for this .
+ /// Creates the download request for this .
///
- /// The to be downloaded.
+ /// The to be downloaded.
/// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.
/// The request object.
protected abstract ArchiveDownloadRequest CreateDownloadRequest(TModel model, bool minimiseDownloadSize);
///
- /// Begin a download for the requested .
+ /// Begin a download for the requested .
///
- /// The to be downloaded.
+ /// The to be downloaded.
/// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.
/// Whether the download was started.
public bool Download(TModel model, bool minimiseDownloadSize = false)
@@ -131,9 +131,9 @@ namespace osu.Game.Database
///
/// Performs implementation specific comparisons to determine whether a given model is present in the local store.
///
- /// The whose existence needs to be checked.
+ /// The whose existence needs to be checked.
/// The usable items present in the store.
- /// Whether the exists.
+ /// Whether the exists.
protected abstract bool CheckLocalAvailability(TModel model, IQueryable items);
public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model));
diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs
index f6f4b0aa42..17f1ccab06 100644
--- a/osu.Game/Database/IModelDownloader.cs
+++ b/osu.Game/Database/IModelDownloader.cs
@@ -14,34 +14,34 @@ namespace osu.Game.Database
where TModel : class
{
///
- /// Fired when a download begins.
+ /// Fired when a download begins.
///
event Action> DownloadBegan;
///
- /// Fired when a download is interrupted, either due to user cancellation or failure.
+ /// Fired when a download is interrupted, either due to user cancellation or failure.
///
event Action> DownloadFailed;
///
- /// Checks whether a given is already available in the local store.
+ /// Checks whether a given is already available in the local store.
///
- /// The whose existence needs to be checked.
- /// Whether the exists.
+ /// The whose existence needs to be checked.
+ /// Whether the exists.
bool IsAvailableLocally(TModel model);
///
- /// Begin a download for the requested .
+ /// Begin a download for the requested .
///
- /// The to be downloaded.
+ /// The to be downloaded.
/// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle..
/// Whether the download was started.
bool Download(TModel model, bool minimiseDownloadSize);
///
- /// Gets an existing download request if it exists.
+ /// Gets an existing download request if it exists.
///
- /// The whose request is wanted.
+ /// The whose request is wanted.
/// The object if it exists, otherwise null.
ArchiveDownloadRequest GetExistingDownload(TModel model);
}
diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs
index 884814cb38..1bdbbb48e6 100644
--- a/osu.Game/Database/IModelManager.cs
+++ b/osu.Game/Database/IModelManager.cs
@@ -6,7 +6,7 @@ using System;
namespace osu.Game.Database
{
///
- /// Represents a model manager that publishes events when s are added or removed.
+ /// Represents a model manager that publishes events when s are added or removed.
///
/// The model type.
public interface IModelManager
diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs
index 39a48b5be6..4ca1eef989 100644
--- a/osu.Game/Database/MutableDatabaseBackedStore.cs
+++ b/osu.Game/Database/MutableDatabaseBackedStore.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Database
public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set());
///
- /// Add a to the database.
+ /// Add a to the database.
///
/// The item to add.
public void Add(T item)
@@ -45,7 +45,7 @@ namespace osu.Game.Database
}
///
- /// Update a in the database.
+ /// Update a in the database.
///
/// The item to update.
public void Update(T item)
@@ -58,7 +58,7 @@ namespace osu.Game.Database
}
///
- /// Delete a from the database.
+ /// Delete a from the database.
///
/// The item to delete.
public bool Delete(T item)
@@ -77,7 +77,7 @@ namespace osu.Game.Database
}
///
- /// Restore a from a deleted state.
+ /// Restore a from a deleted state.
///
/// The item to undelete.
public bool Undelete(T item)
diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
new file mode 100644
index 0000000000..387e189dc4
--- /dev/null
+++ b/osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Graphics.Backgrounds
+{
+ public class BeatmapBackground : Background
+ {
+ public readonly WorkingBeatmap Beatmap;
+
+ private readonly string fallbackTextureName;
+
+ public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1")
+ {
+ Beatmap = beatmap;
+ this.fallbackTextureName = fallbackTextureName;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index dffa0c4fd5..6d88808150 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Graphics.Backgrounds
float adjustedAlpha = HideAlphaDiscrepancies
// Cubically scale alpha to make it drop off more sharply.
- ? (float)Math.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3)
+ ? MathF.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3)
: 1;
float elapsedSeconds = (float)Time.Elapsed / 1000;
diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index 15068d81c0..61391b7102 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -8,9 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Graphics;
-using osu.Framework.Logging;
-using osu.Game.Overlays;
-using osu.Game.Overlays.Notifications;
using osu.Game.Users;
namespace osu.Game.Graphics.Containers
@@ -23,21 +20,12 @@ namespace osu.Game.Graphics.Containers
}
private OsuGame game;
- private ChannelManager channelManager;
- private Action showNotImplementedError;
[BackgroundDependencyLoader(true)]
- private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager)
+ private void load(OsuGame game)
{
// will be null in tests
this.game = game;
- this.channelManager = channelManager;
-
- showNotImplementedError = () => notifications?.Post(new SimpleNotification
- {
- Text = @"This link type is not yet supported!",
- Icon = FontAwesome.Solid.LifeRing,
- });
}
public void AddLinks(string text, List links)
@@ -56,85 +44,47 @@ namespace osu.Game.Graphics.Containers
foreach (var link in links)
{
AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd));
- AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument);
+ AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
previousLinkEnd = link.Index + link.Length;
}
AddText(text.Substring(previousLinkEnd));
}
- public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null)
- => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText);
+ public void AddLink(string text, string url, Action creationParameters = null) =>
+ createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url);
- public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null)
- => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action);
+ public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null)
+ => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action);
- public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null)
+ public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null)
+ => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null);
+
+ public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null)
{
foreach (var t in text)
AddArbitraryDrawable(t);
- return createLink(text, null, url, linkType, linkArgument, tooltipText);
+ createLink(text, new LinkDetails(action, linkArgument), tooltipText);
}
- public IEnumerable AddUserLink(User user, Action creationParameters = null)
- => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile");
+ public void AddUserLink(User user, Action creationParameters = null)
+ => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile");
- private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null)
+ private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null)
{
AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList())
{
RelativeSizeAxes = Axes.Both,
- TooltipText = tooltipText ?? (url != text ? url : string.Empty),
- Action = action ?? (() =>
+ TooltipText = tooltipText,
+ Action = () =>
{
- switch (linkType)
- {
- case LinkAction.OpenBeatmap:
- // TODO: proper query params handling
- if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId))
- game?.ShowBeatmap(beatmapId);
- break;
-
- case LinkAction.OpenBeatmapSet:
- if (int.TryParse(linkArgument, out int setId))
- game?.ShowBeatmapSet(setId);
- break;
-
- case LinkAction.OpenChannel:
- try
- {
- channelManager?.OpenChannel(linkArgument);
- }
- catch (ChannelNotFoundException)
- {
- Logger.Log($"The requested channel \"{linkArgument}\" does not exist");
- }
-
- break;
-
- case LinkAction.OpenEditorTimestamp:
- case LinkAction.JoinMultiplayerMatch:
- case LinkAction.Spectate:
- showNotImplementedError?.Invoke();
- break;
-
- case LinkAction.External:
- game?.OpenUrlExternally(url);
- break;
-
- case LinkAction.OpenUserProfile:
- if (long.TryParse(linkArgument, out long userId))
- game?.ShowUser(userId);
- break;
-
- default:
- throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action.");
- }
- }),
+ if (action != null)
+ action();
+ else
+ game.HandleLink(link);
+ },
});
-
- return drawables;
}
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs
index f65a0a469a..bf743b90ed 100644
--- a/osu.Game/Graphics/Containers/ParallaxContainer.cs
+++ b/osu.Game/Graphics/Containers/ParallaxContainer.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 osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Input;
@@ -48,7 +49,7 @@ namespace osu.Game.Graphics.Containers
if (!parallaxEnabled.Value)
{
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
- content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount));
+ content.Scale = new Vector2(1 + Math.Abs(ParallaxAmount));
}
};
}
@@ -69,10 +70,12 @@ namespace osu.Game.Graphics.Containers
{
Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.Position) - DrawSize / 2) * ParallaxAmount;
- double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
+ const float parallax_duration = 100;
- content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
- content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
+ double elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallax_duration);
+
+ content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, parallax_duration, Easing.OutQuint);
+ content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
}
firstUpdate = false;
diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs
index e5a6bcc28e..dca9df1e98 100644
--- a/osu.Game/Graphics/Containers/ShakeContainer.cs
+++ b/osu.Game/Graphics/Containers/ShakeContainer.cs
@@ -43,9 +43,11 @@ namespace osu.Game.Graphics.Containers
// if we don't have enough time for the second shake, skip it.
if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4)
+ {
sequence = sequence
.MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then()
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
+ }
sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine);
}
diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
index ed771bb03f..cd988c347b 100644
--- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Sprites
public static class OsuSpriteTextTransformExtensions
{
///
- /// Sets to a new value after a duration.
+ /// Sets Text to a new value after a duration.
///
/// A to which further transforms can be added.
public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None)
@@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Sprites
=> spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing);
///
- /// Sets to a new value after a duration.
+ /// Sets Text to a new value after a duration.
///
/// A to which further transforms can be added.
public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None)
diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs
index f8d5955503..0be928cf83 100644
--- a/osu.Game/Graphics/UserInterface/Bar.cs
+++ b/osu.Game/Graphics/UserInterface/Bar.cs
@@ -1,12 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using System;
namespace osu.Game.Graphics.UserInterface
{
@@ -29,7 +29,7 @@ namespace osu.Game.Graphics.UserInterface
get => length;
set
{
- length = MathHelper.Clamp(value, 0, 1);
+ length = Math.Clamp(value, 0, 1);
updateBarLength();
}
}
diff --git a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs
index b7d2222f33..f2f6dd429b 100644
--- a/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs
+++ b/osu.Game/Graphics/UserInterface/DimmedLoadingLayer.cs
@@ -6,16 +6,17 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Extensions.Color4Extensions;
+using osuTK;
namespace osu.Game.Graphics.UserInterface
{
- public class DimmedLoadingLayer : VisibilityContainer
+ public class DimmedLoadingLayer : OverlayContainer
{
private const float transition_duration = 250;
private readonly LoadingAnimation loading;
- public DimmedLoadingLayer()
+ public DimmedLoadingLayer(float dimAmount = 0.5f, float iconScale = 1f)
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
@@ -23,9 +24,9 @@ namespace osu.Game.Graphics.UserInterface
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black.Opacity(0.5f),
+ Colour = Color4.Black.Opacity(dimAmount),
},
- loading = new LoadingAnimation(),
+ loading = new LoadingAnimation { Scale = new Vector2(iconScale) },
};
}
diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs
new file mode 100644
index 0000000000..591ed3df83
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs
@@ -0,0 +1,133 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.Sprites;
+using osuTK.Graphics;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public class DrawableOsuMenuItem : Menu.DrawableMenuItem
+ {
+ public const int MARGIN_HORIZONTAL = 17;
+ public const int MARGIN_VERTICAL = 4;
+ private const int text_size = 17;
+ private const int transition_length = 80;
+
+ private SampleChannel sampleClick;
+ private SampleChannel sampleHover;
+
+ private TextContainer text;
+
+ public DrawableOsuMenuItem(MenuItem item)
+ : base(item)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sampleHover = audio.Samples.Get(@"UI/generic-hover");
+ sampleClick = audio.Samples.Get(@"UI/generic-select");
+
+ BackgroundColour = Color4.Transparent;
+ BackgroundColourHover = OsuColour.FromHex(@"172023");
+
+ updateTextColour();
+ }
+
+ private void updateTextColour()
+ {
+ switch ((Item as OsuMenuItem)?.Type)
+ {
+ default:
+ case MenuItemType.Standard:
+ text.Colour = Color4.White;
+ break;
+
+ case MenuItemType.Destructive:
+ text.Colour = Color4.Red;
+ break;
+
+ case MenuItemType.Highlighted:
+ text.Colour = OsuColour.FromHex(@"ffcc22");
+ break;
+ }
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ sampleHover.Play();
+ text.BoldText.FadeIn(transition_length, Easing.OutQuint);
+ text.NormalText.FadeOut(transition_length, Easing.OutQuint);
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ text.BoldText.FadeOut(transition_length, Easing.OutQuint);
+ text.NormalText.FadeIn(transition_length, Easing.OutQuint);
+ base.OnHoverLost(e);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ sampleClick.Play();
+ return base.OnClick(e);
+ }
+
+ protected sealed override Drawable CreateContent() => text = CreateTextContainer();
+ protected virtual TextContainer CreateTextContainer() => new TextContainer();
+
+ protected class TextContainer : Container, IHasText
+ {
+ public string Text
+ {
+ get => NormalText.Text;
+ set
+ {
+ NormalText.Text = value;
+ BoldText.Text = value;
+ }
+ }
+
+ public readonly SpriteText NormalText;
+ public readonly SpriteText BoldText;
+
+ public TextContainer()
+ {
+ Anchor = Anchor.CentreLeft;
+ Origin = Anchor.CentreLeft;
+
+ AutoSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ NormalText = new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(size: text_size),
+ Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL },
+ },
+ BoldText = new OsuSpriteText
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold),
+ Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL },
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs
new file mode 100644
index 0000000000..3dc99f2dbe
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.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 osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osuTK;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public class DrawableStatefulMenuItem : DrawableOsuMenuItem
+ {
+ protected new StatefulMenuItem Item => (StatefulMenuItem)base.Item;
+
+ public DrawableStatefulMenuItem(StatefulMenuItem item)
+ : base(item)
+ {
+ }
+
+ protected override TextContainer CreateTextContainer() => new ToggleTextContainer(Item);
+
+ private class ToggleTextContainer : TextContainer
+ {
+ private readonly StatefulMenuItem menuItem;
+ private readonly Bindable