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..8cdb92d11c 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 = true:warning
+csharp_style_prefer_range_operator = true:warning
+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/.gitattributes b/.gitattributes
index e3ffd343db..c61ca25cd2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,6 +15,7 @@ App.config text eol=crlf
*.cmd text eol=crlf
*.snippet text eol=crlf
*.manifest text eol=crlf
+*.licenseheader text eol=crlf
# Check out with lf (UNIX) line endings
*.sh text eol=lf
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..0c6b80e97e
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://osu.ppy.sh/home/support
diff --git a/.github/ISSUE_TEMPLATE/00-mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md
new file mode 100644
index 0000000000..f171e80b8b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/00-mobile-issues.md
@@ -0,0 +1,8 @@
+---
+name: Mobile Report
+about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!)
+---
+
+⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them.
+If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome.
+Otherwise, please check back in the future when the focus of development shifts towards mobile!
diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
new file mode 100644
index 0000000000..c8c41e5a78
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md
@@ -0,0 +1,11 @@
+---
+name: Bug Report
+about: Issues regarding encountered bugs.
+---
+**Describe the bug:**
+
+**Screenshots or videos showing encountered issue:**
+
+**osu!lazer version:**
+
+**Logs:**
diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md
new file mode 100644
index 0000000000..8ad27e9e31
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md
@@ -0,0 +1,13 @@
+---
+name: Crash Report
+about: Issues regarding crashes or permanent freezes.
+---
+**Describe the crash:**
+
+**Screenshots or videos showing encountered issue:**
+
+**osu!lazer version:**
+
+**Logs:**
+
+**Computer Specifications:**
diff --git a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md b/.github/ISSUE_TEMPLATE/03-feature-request-issues.md
new file mode 100644
index 0000000000..54c4ff94e5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03-feature-request-issues.md
@@ -0,0 +1,7 @@
+---
+name: Feature Request
+about: Features you would like to see in the game!
+---
+**Describe the new feature:**
+
+**Proposal designs of the feature:**
diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md
deleted file mode 100644
index 8d85c92fec..0000000000
--- a/.github/ISSUE_TEMPLATE/bug-issues.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: Bug Report
-about: For issues regarding encountered game bugs
----
-
-
-
-**Describe your problem:**
-
-**Screenshots or videos showing encountered issue:**
-
-**osu!lazer version:**
-
-**Logs:**
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..69baeee60c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: osu!stable issues
+ url: https://github.com/ppy/osu-stable-issues
+ about: For issues regarding osu!stable (not osu!lazer), open them here.
diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md
deleted file mode 100644
index 849f042c1f..0000000000
--- a/.github/ISSUE_TEMPLATE/crash-issues.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-name: Crash Report
-about: For issues regarding game crashes or permanent freezes
----
-
-
-
-**Describe your problem:**
-
-**Screenshots or videos showing encountered issue:**
-
-**osu!lazer version:**
-
-**Logs:**
-
-**Computer Specifications:**
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md
deleted file mode 100644
index 73c4f37a3e..0000000000
--- a/.github/ISSUE_TEMPLATE/feature-request-issues.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-name: Feature Request
-about: Let us know what you would like to see in the game!
----
-
-
-
-**Describe the feature:**
-
-**Proposal designs of the feature:**
diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md
deleted file mode 100644
index ae3cf20a8c..0000000000
--- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-name: Missing for Live
-about: Let us know the features you need which are available in osu-stable but not lazer
----
-
-
-
-**Describe the feature:**
-
-**Designs:**
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
deleted file mode 100644
index 221e4746cb..0000000000
--- a/.github/pull_request_template.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Add any details pertaining to developers above the break.
-
-- [ ] Depends on #PR
-- Closes #ISSUE
-
----
-
-Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f95a04e517..732b171f69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,12 +10,8 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
-### Cake ###
-tools/*
-!tools/cakebuild.csproj
-
# Build results
-bin/[Dd]ebug/
+[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
@@ -102,7 +98,6 @@ $tf/
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-inspectcode
# JustCode is a .NET coding add-in
.JustCode
@@ -196,6 +191,7 @@ ClientBin/
*.publishsettings
node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@@ -251,20 +247,90 @@ 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
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index f1c4f5d172..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "osu-resources"]
- path = osu-resources
- url = https://github.com/ppy/osu-resources
\ No newline at end of file
diff --git a/.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.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
new file mode 100644
index 0000000000..1815c271b4
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
similarity index 68%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 1c988fe6fd..a4154623b6 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
-
+
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
similarity index 68%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index d7bb0f90f1..080dc04001 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
-
+
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
similarity index 68%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 997ac6b078..3de6a7e609 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
-
+
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
similarity index 68%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index b7a070174c..da14c2a29e 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
-
+
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
new file mode 100644
index 0000000000..45d1ce25e9
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
new file mode 100644
index 0000000000..ba80f7c100
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
similarity index 86%
rename from .idea/.idea.osu/.idea/runConfigurations/osu_.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
index 2735f4ceb3..911c3ed9b7 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
new file mode 100644
index 0000000000..d85a0ae44c
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
similarity index 67%
rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
rename to .idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
index bf5a1f64e0..ec3c81f4cd 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
@@ -1,17 +1,20 @@
-
-
+
+
+
-
-
+
+
+
+
\ No newline at end of file
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/.travis.yml b/.travis.yml
deleted file mode 100644
index ce353d9b27..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-language: csharp
-solution: osu.sln
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index e9b8d6f397..6480612b2e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,53 +1,19 @@
{
"version": "0.2.0",
"configurations": [
- {
- "name": "VisualTests (Debug)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
- {
- "name": "VisualTests (Release)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
{
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -58,16 +24,149 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
+ },
+ {
+ "name": "osu! (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "osu! (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Benchmark",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll",
+ "args": [
+ "--filter",
+ "*"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build benchmarks",
+ "console": "internalConsole"
+ },
+ {
+ "name": "Cake: Debug Script",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll",
+ "args": [
+ "${workspaceRoot}/build/build.cake",
+ "--debug",
+ "--verbosity=diagnostic"
+ ],
+ "cwd": "${workspaceRoot}/build",
+ "stopAtEntry": true,
+ "externalConsole": false
}
]
-}
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 188f20b69f..e638dec767 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -11,7 +11,6 @@
"build",
"--no-restore",
"osu.Desktop",
- "/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -27,7 +26,6 @@
"build",
"--no-restore",
"osu.Desktop",
- "/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -44,7 +42,6 @@
"build",
"--no-restore",
"osu.Game.Tests",
- "/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -60,7 +57,6 @@
"build",
"--no-restore",
"osu.Game.Tests",
- "/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -70,11 +66,59 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Restore (netcoreapp2.1)",
+ "label": "Build tournament tests (Debug)",
"type": "shell",
"command": "dotnet",
"args": [
- "restore"
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Build tournament tests (Release)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Build benchmarks",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Benchmarks",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Restore (netcoreapp3.1)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "restore",
+ "build/Desktop.proj"
],
"problemMatcher": []
}
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
new file mode 100644
index 0000000000..a92191a439
--- /dev/null
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -0,0 +1,6 @@
+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.
+M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == 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/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
new file mode 100644
index 0000000000..d497365f87
--- /dev/null
+++ b/CodeAnalysis/osu.ruleset
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..21b8b402e0
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,46 @@
+
+
+
+ 8.0
+ true
+
+
+ $(MSBuildThisFileDirectory)app.manifest
+
+
+
+ osu.licenseheader
+
+
+
+
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
+
+
+ true
+ $(NoWarn);CS1591
+
+
+
+ $(NoWarn);NU1701
+
+
+ false
+ ppy Pty Ltd
+ MIT
+ https://github.com/ppy/osu
+ https://github.com/ppy/osu
+ Automated release.
+ ppy Pty Ltd
+ Copyright (c) 2020 ppy Pty Ltd
+ osu game
+
+
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000..cdd3a6b349
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+gem "fastlane"
+
+plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
+eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..e3954c2681
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,175 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.2)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ atomos (0.1.3)
+ babosa (1.0.3)
+ claide (1.0.3)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander-fastlane (4.4.6)
+ highline (~> 1.7.2)
+ declarative (0.0.10)
+ declarative-option (0.1.0)
+ digest-crc (0.4.1)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.5)
+ emoji_regex (1.0.1)
+ excon (0.71.1)
+ faraday (0.17.3)
+ multipart-post (>= 1.2, < 3)
+ faraday-cookie_jar (0.0.6)
+ faraday (>= 0.7.4)
+ http-cookie (~> 1.0.0)
+ faraday_middleware (0.13.1)
+ faraday (>= 0.7.4, < 1.0)
+ fastimage (2.1.7)
+ fastlane (2.140.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.3, < 3.0.0)
+ babosa (>= 1.0.2, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander-fastlane (>= 4.4.6, < 5.0.0)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 2.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 0.17)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 0.13.1)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-api-client (>= 0.29.2, < 0.37.0)
+ google-cloud-storage (>= 1.15.0, < 2.0.0)
+ highline (>= 1.7.2, < 2.0.0)
+ json (< 3.0.0)
+ jwt (~> 2.1.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multi_xml (~> 0.5)
+ multipart-post (~> 2.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ public_suffix (~> 2.0.0)
+ rubyzip (>= 1.3.0, < 2.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ slack-notifier (>= 2.0.0, < 3.0.0)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ fastlane-plugin-clean_testflight_testers (0.3.0)
+ fastlane-plugin-souyuz (0.9.1)
+ souyuz (= 0.9.1)
+ fastlane-plugin-xamarin (0.6.3)
+ gh_inspector (1.1.3)
+ google-api-client (0.36.4)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (~> 0.9)
+ httpclient (>= 2.8.1, < 3.0)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ signet (~> 0.12)
+ google-cloud-core (1.5.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.3.0)
+ faraday (~> 0.11)
+ google-cloud-errors (1.0.0)
+ google-cloud-storage (1.25.1)
+ addressable (~> 2.5)
+ digest-crc (~> 0.4)
+ google-api-client (~> 0.33)
+ google-cloud-core (~> 1.2)
+ googleauth (~> 0.9)
+ mini_mime (~> 1.0)
+ googleauth (0.10.0)
+ faraday (~> 0.12)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (~> 0.12)
+ highline (1.7.10)
+ http-cookie (1.0.3)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ json (2.3.0)
+ jwt (2.1.0)
+ memoist (0.16.2)
+ mini_magick (4.10.1)
+ mini_mime (1.0.2)
+ mini_portile2 (2.4.0)
+ multi_json (1.14.1)
+ multi_xml (0.6.0)
+ multipart-post (2.0.0)
+ nanaimo (0.2.6)
+ naturally (2.2.0)
+ nokogiri (1.10.7)
+ mini_portile2 (~> 2.4.0)
+ os (1.0.1)
+ plist (3.5.0)
+ public_suffix (2.0.5)
+ representable (3.0.4)
+ declarative (< 0.1.0)
+ declarative-option (< 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rouge (2.0.7)
+ rubyzip (1.3.0)
+ security (0.1.3)
+ signet (0.12.0)
+ addressable (~> 2.3)
+ faraday (~> 0.9)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.7)
+ CFPropertyList
+ naturally
+ slack-notifier (2.3.2)
+ souyuz (0.9.1)
+ fastlane (>= 1.103.0)
+ highline (~> 1.7)
+ nokogiri (~> 1.7)
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ tty-cursor (0.7.0)
+ tty-screen (0.7.0)
+ tty-spinner (0.9.2)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.7.6)
+ unicode-display_width (1.6.1)
+ word_wrap (1.0.0)
+ xcodeproj (1.14.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.2.6)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.0)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ fastlane
+ fastlane-plugin-clean_testflight_testers
+ fastlane-plugin-souyuz
+ fastlane-plugin-xamarin
+
+BUNDLED WITH
+ 2.0.1
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
deleted file mode 100644
index 2bff304fba..0000000000
--- a/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-osu!lazer is currently still under heavy development!
-
-Please ensure that you are making an issue for one of the following:
-
-- A bug with currently implemented features (not features that don't exist)
-- A feature you are considering adding, so we can collaborate on feedback and design.
-- Discussions about technical design decisions
-
-If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide.
-
-Screenshots and log files are highly welcomed.
\ No newline at end of file
diff --git a/InspectCode.ps1 b/InspectCode.ps1
new file mode 100644
index 0000000000..6ed935fdbb
--- /dev/null
+++ b/InspectCode.ps1
@@ -0,0 +1,27 @@
+[CmdletBinding()]
+Param(
+ [string]$Target,
+ [string]$Configuration,
+ [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
+ [string]$Verbosity,
+ [switch]$ShowDescription,
+ [Alias("WhatIf", "Noop")]
+ [switch]$DryRun,
+ [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
+ [string[]]$ScriptArgs
+)
+
+# Build Cake arguments
+$cakeArguments = "";
+if ($Target) { $cakeArguments += "-target=$Target" }
+if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
+if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
+if ($ShowDescription) { $cakeArguments += "-showdescription" }
+if ($DryRun) { $cakeArguments += "-dryrun" }
+if ($Experimental) { $cakeArguments += "-experimental" }
+$cakeArguments += $ScriptArgs
+
+dotnet tool restore
+dotnet cake ./build/InspectCode.cake --bootstrap
+dotnet cake ./build/InspectCode.cake $cakeArguments
+exit $LASTEXITCODE
\ No newline at end of file
diff --git a/LICENCE b/LICENCE
index a11a7ce75b..2435c23545 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2018 ppy Pty Ltd .
+Copyright (c) 2020 ppy Pty Ltd .
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index baaba22726..67027bb9f3 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,108 @@
-# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
+
+
+
-Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
+# osu!
-# Status
+[](https://ci.appveyor.com/project/peppy/osu)
+[]()
+[](https://www.codefactor.io/repository/github/ppy/osu)
+[](https://discord.gg/ppy)
-This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
+Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
+
+## Status
+
+This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
-# Requirements
+Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog).
-- A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed.
-- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial).
+## Requirements
-# Building and running
+- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
+- 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/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
+- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
+- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
-If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform).
+## Running osu!
-Clone the repository including submodules
+### Releases
-`git clone --recurse-submodules https://github.com/ppy/osu`
+If you are not interested in developing the game, you can still consume our [binary releases](https://github.com/ppy/osu/releases).
-Build and run
+**Latest build:**
-- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
-- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance.
-- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice.
+| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| ------------- | ------------- | ------------- | ------------- |
-Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example
+- **Linux** users are recommended to self-compile until we have official deployment in place.
-If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build.
+If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
-# Contributing
+### Downloading the source code
-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.
+Clone the repository:
-Please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
+```shell
+git clone https://github.com/ppy/osu
+cd osu
+```
-Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://www.bountysource.com/teams/ppy). If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues).
+To update the source code to the latest commit, run the following command inside the `osu` directory:
-Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
+```shell
+git pull
+```
-# Licence
+### Building
-The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
+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 is provided [below](#contributing).
+
+- 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:
+
+```shell
+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`.
+
+_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._
+
+### Testing with resource/framework modifications
+
+Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
+
+### Code analysis
+
+Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
+
+We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
+
+JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](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 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).
+
+Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
+
+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, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless 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.
+
+## Licence
+
+*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
diff --git a/appveyor.yml b/appveyor.yml
index 1f485485da..a4a0cedc66 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,8 +1,27 @@
clone_depth: 1
version: '{branch}-{build}'
-image: Visual Studio 2017
-test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
-build_script:
- - cmd: PowerShell -Version 2.0 .\build.ps1
+image: Visual Studio 2019
+dotnet_csproj:
+ patch: true
+ file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
+ version: '0.0.{build}'
+cache:
+ - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
+before_build:
+ - ps: dotnet --info # Useful when version mismatch between CI and local
+ - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
+build:
+ project: osu.sln
+ parallel: true
+ verbosity: minimal
+ publish_nuget: true
+after_build:
+ - ps: dotnet tool restore
+ - ps: dotnet format --dry-run --check
+ - ps: .\InspectCode.ps1
+test:
+ assemblies:
+ except:
+ - '**\*Android*'
+ - '**\*iOS*'
+ - 'build\**\*'
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..bb4482f501
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,21 @@
+clone_depth: 1
+version: '{build}'
+image: Visual Studio 2019
+dotnet_csproj:
+ patch: true
+ file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
+ version: $(APPVEYOR_REPO_TAG_NAME)
+before_build:
+ - ps: dotnet --info # Useful when version mismatch between CI and local
+ - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
+test: off
+skip_non_tags: true
+configuration: Release
+build:
+ project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
+ parallel: true
+ verbosity: minimal
+ publish_nuget: true
+deploy:
+ - provider: Environment
+ name: nuget
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/assets/lazer.png b/assets/lazer.png
new file mode 100644
index 0000000000..1e40e844cc
Binary files /dev/null and b/assets/lazer.png differ
diff --git a/build.cake b/build.cake
deleted file mode 100644
index bc7dfafb8c..0000000000
--- a/build.cake
+++ /dev/null
@@ -1,72 +0,0 @@
-#addin "nuget:?package=CodeFileSanity&version=0.0.21"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2"
-#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
-
-///////////////////////////////////////////////////////////////////////////////
-// ARGUMENTS
-///////////////////////////////////////////////////////////////////////////////
-
-var target = Argument("target", "Build");
-var configuration = Argument("configuration", "Release");
-
-var osuSolution = new FilePath("./osu.sln");
-
-///////////////////////////////////////////////////////////////////////////////
-// TASKS
-///////////////////////////////////////////////////////////////////////////////
-
-Task("Restore")
- .Does(() => {
- DotNetCoreRestore(osuSolution.FullPath);
- });
-
-Task("Compile")
- .IsDependentOn("Restore")
- .Does(() => {
- DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings {
- Configuration = configuration,
- NoRestore = true,
- });
- });
-
-Task("Test")
- .IsDependentOn("Compile")
- .Does(() => {
- var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll");
-
- DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
- Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
- Parallel = true,
- ToolTimeout = TimeSpan.FromMinutes(10),
- });
- });
-
-// windows only because both inspectcore and nvika depend on net45
-Task("InspectCode")
- .WithCriteria(IsRunningOnWindows())
- .IsDependentOn("Compile")
- .Does(() => {
- var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
-
- InspectCode(osuSolution, new InspectCodeSettings {
- CachesHome = "inspectcode",
- OutputFile = "inspectcodereport.xml",
- });
-
- StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
- });
-
-Task("CodeFileSanity")
- .Does(() => {
- ValidateCodeSanity(new ValidateCodeSanitySettings {
- RootDirectory = ".",
- IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
- });
- });
-
-Task("Build")
- .IsDependentOn("CodeFileSanity")
- .IsDependentOn("InspectCode")
- .IsDependentOn("Test");
-
-RunTarget(target);
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
deleted file mode 100644
index 9968673c90..0000000000
--- a/build.ps1
+++ /dev/null
@@ -1,79 +0,0 @@
-##########################################################################
-# This is a customized Cake bootstrapper script for PowerShell.
-##########################################################################
-
-<#
-
-.SYNOPSIS
-This is a Powershell script to bootstrap a Cake build.
-
-.DESCRIPTION
-This Powershell script restores NuGet tools (including Cake)
-and execute your Cake build script with the parameters you provide.
-
-.PARAMETER Script
-The build script to execute.
-.PARAMETER Target
-The build script target to run.
-.PARAMETER Configuration
-The build configuration to use.
-.PARAMETER Verbosity
-Specifies the amount of information to be displayed.
-.PARAMETER ShowDescription
-Shows description about tasks.
-.PARAMETER DryRun
-Performs a dry run.
-.PARAMETER ScriptArgs
-Remaining arguments are added here.
-
-.LINK
-https://cakebuild.net
-
-#>
-
-[CmdletBinding()]
-Param(
- [string]$Script = "build.cake",
- [string]$Target,
- [string]$Configuration,
- [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
- [string]$Verbosity,
- [switch]$ShowDescription,
- [Alias("WhatIf", "Noop")]
- [switch]$DryRun,
- [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
- [string[]]$ScriptArgs
-)
-
-Write-Host "Preparing to run build script..."
-
-# Determine the script root for resolving other paths.
-if(!$PSScriptRoot){
- $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
-}
-
-# Resolve the paths for resources used for debugging.
-$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
-$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj"
-
-# Install the required tools locally.
-Write-Host "Restoring cake tools..."
-Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
-
-# Find the Cake executable
-$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName
-
-# Build Cake arguments
-$cakeArguments = @("$Script");
-if ($Target) { $cakeArguments += "-target=$Target" }
-if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
-if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
-if ($ShowDescription) { $cakeArguments += "-showdescription" }
-if ($DryRun) { $cakeArguments += "-dryrun" }
-if ($Experimental) { $cakeArguments += "-experimental" }
-$cakeArguments += $ScriptArgs
-
-# Start Cake
-Write-Host "Running build script..."
-Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
-exit $LASTEXITCODE
diff --git a/build.sh b/build.sh
deleted file mode 100755
index caf1702f41..0000000000
--- a/build.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-
-##########################################################################
-# This is a customized Cake bootstrapper script for Shell.
-##########################################################################
-
-echo "Preparing to run build script..."
-
-SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
-TOOLS_DIR=$SCRIPT_DIR/tools
-CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
-
-SCRIPT="build.cake"
-CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj"
-
-# Parse arguments.
-CAKE_ARGUMENTS=()
-for i in "$@"; do
- case $1 in
- -s|--script) SCRIPT="$2"; shift ;;
- --) shift; CAKE_ARGUMENTS+=("$@"); break ;;
- *) CAKE_ARGUMENTS+=("$1") ;;
- esac
- shift
-done
-
-# Install the required tools locally.
-echo "Restoring cake tools..."
-dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1
-
-# Search for the CakeBuild binary.
-CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll")
-
-# Start Cake
-echo "Running build script..."
-
-dotnet "$CAKE_BINARY" $SCRIPT "${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/InspectCode.cake b/build/InspectCode.cake
new file mode 100644
index 0000000000..06c56dce87
--- /dev/null
+++ b/build/InspectCode.cake
@@ -0,0 +1,52 @@
+#addin "nuget:?package=CodeFileSanity&version=0.0.33"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0"
+#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
+var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
+
+///////////////////////////////////////////////////////////////////////////////
+// ARGUMENTS
+///////////////////////////////////////////////////////////////////////////////
+
+var target = Argument("target", "CodeAnalysis");
+var configuration = Argument("configuration", "Release");
+
+var rootDirectory = new DirectoryPath("..");
+var sln = rootDirectory.CombineWithFilePath("osu.sln");
+var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
+
+///////////////////////////////////////////////////////////////////////////////
+// TASKS
+///////////////////////////////////////////////////////////////////////////////
+
+// windows only because both inspectcode and nvika depend on net45
+Task("InspectCode")
+ .WithCriteria(IsRunningOnWindows())
+ .Does(() => {
+ InspectCode(desktopSlnf, new InspectCodeSettings {
+ CachesHome = "inspectcode",
+ OutputFile = "inspectcodereport.xml",
+ ArgumentCustomization = arg => {
+ if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output
+ arg.Append("--verbosity:WARN");
+ return arg;
+ },
+ });
+
+ int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
+ if (returnCode != 0)
+ throw new Exception($"inspectcode failed with return code {returnCode}");
+ });
+
+Task("CodeFileSanity")
+ .Does(() => {
+ ValidateCodeSanity(new ValidateCodeSanitySettings {
+ RootDirectory = rootDirectory.FullPath,
+ IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
+ });
+ });
+
+Task("CodeAnalysis")
+ .IsDependentOn("CodeFileSanity")
+ .IsDependentOn("InspectCode");
+
+RunTarget(target);
\ No newline at end of file
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000000..083de66985
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,2 @@
+app_identifier("sh.ppy.osulazer") # The bundle identifier of your app
+apple_id("apple-dev@ppy.sh") # Your Apple email address
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000000..28a83fbbae
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,140 @@
+update_fastlane
+
+platform :android do
+desc 'Deploy to play store'
+ lane :beta do |options|
+
+ update_version(
+ version: options[:version],
+ build: options[:build],
+ )
+
+ build(options)
+
+ supply(
+ apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk',
+ package_name: 'sh.ppy.osulazer',
+ track: 'alpha', # upload to alpha, we can promote it later
+ json_key: options[:json_key],
+ )
+ end
+
+ desc 'Deploy to github release'
+ lane :build_github do |options|
+
+ update_version(
+ version: options[:version],
+ build: options[:build],
+ )
+
+ build(options)
+
+ client = HTTPClient.new
+ changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw'
+ changelog.gsub!('$BUILD_ID', options[:build])
+
+ set_github_release(
+ repository_name: "ppy/osu",
+ api_token: ENV["GITHUB_TOKEN"],
+ name: options[:build],
+ tag_name: options[:build],
+ is_draft: true,
+ description: changelog,
+ commitish: "master",
+ upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"]
+ )
+
+ end
+
+ desc 'Compile the project'
+ lane :build do |options|
+ nuget_restore(
+ project_path: 'osu.sln'
+ )
+
+ souyuz(
+ build_configuration: 'Release',
+ solution_path: 'osu.sln',
+ platform: "android",
+ output_path: "osu.Android/bin/Release/",
+ keystore_path: options[:keystore_path],
+ keystore_alias: options[:keystore_alias],
+ keystore_password: ENV["KEYSTORE_PASSWORD"]
+ )
+ end
+
+ lane :update_version do |options|
+
+ split = options[:build].split('.')
+ split[1] = split[1].to_s.rjust(4, '0')
+ android_build = split.join('')
+
+ app_version(
+ solution_path: 'osu.sln',
+ version: options[:version],
+ build: android_build,
+ )
+ end
+
+end
+
+platform :ios do
+ desc 'Deploy to testflight'
+ lane :beta do |options|
+ update_version(options)
+
+ provision(
+ type: 'appstore'
+ )
+
+ build(
+ build_configuration: 'Release',
+ build_platform: 'iPhone'
+ )
+
+ client = HTTPClient.new
+ changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw'
+ changelog.gsub!('$BUILD_ID', options[:build])
+
+ pilot(
+ wait_processing_interval: 1800,
+ changelog: changelog,
+ ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
+ )
+ end
+
+ desc 'Compile the project'
+ lane :build do
+ nuget_restore(
+ project_path: 'osu.sln'
+ )
+
+ souyuz(
+ platform: "ios",
+ build_target: "osu_iOS",
+ plist_path: "../osu.iOS/Info.plist"
+ )
+ end
+
+ desc 'Install provisioning profiles using match'
+ lane :provision do |options|
+ if Helper.is_ci?
+ options[:readonly] = true
+ end
+
+ match(options)
+ end
+
+ lane :update_version do |options|
+ options[:plist_path] = '../osu.iOS/Info.plist'
+ app_version(options)
+ end
+
+ lane :testflight_prune_dry do
+ clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ end
+
+ lane :testflight_prune do
+ clean_testflight_testers(days_of_inactivity: 45)
+ end
+end
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
new file mode 100644
index 0000000000..40c974b09e
--- /dev/null
+++ b/fastlane/Matchfile
@@ -0,0 +1 @@
+git_url('https://github.com/peppy/apple-certificates')
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
new file mode 100644
index 0000000000..9f4f47f213
--- /dev/null
+++ b/fastlane/Pluginfile
@@ -0,0 +1,7 @@
+# Autogenerated by fastlane
+#
+# Ensure this file is checked in to source control!
+
+gem 'fastlane-plugin-clean_testflight_testers'
+gem 'fastlane-plugin-souyuz'
+gem 'fastlane-plugin-xamarin'
diff --git a/fastlane/README.md b/fastlane/README.md
new file mode 100644
index 0000000000..a400ed9516
--- /dev/null
+++ b/fastlane/README.md
@@ -0,0 +1,78 @@
+fastlane documentation
+================
+# Installation
+
+Make sure you have the latest version of the Xcode command line tools installed:
+
+```
+xcode-select --install
+```
+
+Install _fastlane_ using
+```
+[sudo] gem install fastlane -NV
+```
+or alternatively using `brew cask install fastlane`
+
+# Available Actions
+## Android
+### android beta
+```
+fastlane android beta
+```
+Deploy to play store
+### android build_github
+```
+fastlane android build_github
+```
+Deploy to github release
+### android build
+```
+fastlane android build
+```
+Compile the project
+### android update_version
+```
+fastlane android update_version
+```
+
+
+----
+
+## iOS
+### ios beta
+```
+fastlane ios beta
+```
+Deploy to testflight
+### ios build
+```
+fastlane ios build
+```
+Compile the project
+### ios provision
+```
+fastlane ios provision
+```
+Install provisioning profiles using match
+### ios update_version
+```
+fastlane ios update_version
+```
+
+### ios testflight_prune_dry
+```
+fastlane ios testflight_prune_dry
+```
+
+### ios testflight_prune
+```
+fastlane ios testflight_prune
+```
+
+
+----
+
+This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
+More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
+The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/global.json b/global.json
new file mode 100644
index 0000000000..6858d4044d
--- /dev/null
+++ b/global.json
@@ -0,0 +1,10 @@
+{
+ "sdk": {
+ "allowPrerelease": false,
+ "rollForward": "minor",
+ "version": "3.1.100"
+ },
+ "msbuild-sdks": {
+ "Microsoft.Build.Traversal": "2.0.24"
+ }
+}
\ No newline at end of file
diff --git a/osu-resources b/osu-resources
deleted file mode 160000
index 694cb03f19..0000000000
--- a/osu-resources
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 694cb03f19c93106ed0f2593f3e506e835fb652a
diff --git a/osu.Android.props b/osu.Android.props
new file mode 100644
index 0000000000..2ccba60424
--- /dev/null
+++ b/osu.Android.props
@@ -0,0 +1,59 @@
+
+
+ 8.0
+ bin\$(Configuration)
+ 4
+ 2.0
+ false
+ false
+ Library
+ 512
+ Off
+ True
+ Xamarin.Android.Net.AndroidClientHandler
+ v10.0
+ false
+ true
+ armeabi-v7a;x86;arm64-v8a
+ true
+ cjk,mideast,other,rare,west
+ SdkOnly
+ prompt
+
+
+ True
+ portable
+ False
+ DEBUG;TRACE
+ false
+ false
+ true
+ false
+
+
+ false
+ None
+ True
+ true
+ false
+ False
+ true
+
+
+
+ osu.licenseheader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
new file mode 100644
index 0000000000..2e5fa59d20
--- /dev/null
+++ b/osu.Android/OsuGameActivity.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+using osu.Framework.Android;
+
+namespace osu.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
+ public class OsuGameActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuGameAndroid();
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ // The default current directory on android is '/'.
+ // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
+ // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
+ System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
+
+ base.OnCreate(savedInstanceState);
+
+ Window.AddFlags(WindowManagerFlags.Fullscreen);
+ Window.AddFlags(WindowManagerFlags.KeepScreenOn);
+ }
+ }
+}
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
new file mode 100644
index 0000000000..a91c010809
--- /dev/null
+++ b/osu.Android/OsuGameAndroid.cs
@@ -0,0 +1,40 @@
+// 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 Android.App;
+using osu.Game;
+using osu.Game.Updater;
+
+namespace osu.Android
+{
+ public class OsuGameAndroid : OsuGame
+ {
+ public override Version AssemblyVersion
+ {
+ get
+ {
+ var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0);
+
+ try
+ {
+ string versionName = packageInfo.VersionCode.ToString();
+ // undo play store version garbling
+ return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
+ }
+ catch
+ {
+ }
+
+ return new Version(packageInfo.VersionName);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Add(new SimpleUpdateManager());
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..770eaf2222
--- /dev/null
+++ b/osu.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Android/Resources/drawable/lazer.png b/osu.Android/Resources/drawable/lazer.png
new file mode 100644
index 0000000000..fc7aa8a092
Binary files /dev/null and b/osu.Android/Resources/drawable/lazer.png differ
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
new file mode 100644
index 0000000000..ac3905a372
--- /dev/null
+++ b/osu.Android/osu.Android.csproj
@@ -0,0 +1,55 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Android
+ osu.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+ cjk;mideast;other;rare;west
+ d8
+ r8
+
+
+
+
+
+
+
+
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {48f4582b-7687-4621-9cbe-5c24197cb536}
+ osu.Game.Rulesets.Mania
+
+
+ {c92a607b-1fdd-4954-9f92-03ff547d9080}
+ osu.Game.Rulesets.Osu
+
+
+ {f167e17a-7de6-4af5-b920-a5112296c695}
+ osu.Game.Rulesets.Taiko
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
new file mode 100644
index 0000000000..d2c14d321a
--- /dev/null
+++ b/osu.Desktop.slnf
@@ -0,0 +1,21 @@
+{
+ "solution": {
+ "path": "osu.sln",
+ "projects": [
+ "osu.Desktop\\osu.Desktop.csproj",
+ "osu.Game.Benchmarks\\osu.Game.Benchmarks.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/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
new file mode 100644
index 0000000000..08cc0e7f5f
--- /dev/null
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -0,0 +1,149 @@
+// 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.Text;
+using DiscordRPC;
+using DiscordRPC.Message;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Online.API;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using LogLevel = osu.Framework.Logging.LogLevel;
+using User = osu.Game.Users.User;
+
+namespace osu.Desktop
+{
+ internal class DiscordRichPresence : Component
+ {
+ private const string client_id = "367827983903490050";
+
+ private DiscordRpcClient client;
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
+ private Bindable user;
+
+ private readonly IBindable status = new Bindable();
+ private readonly IBindable activity = new Bindable();
+
+ private readonly RichPresence presence = new RichPresence
+ {
+ Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider provider)
+ {
+ client = new DiscordRpcClient(client_id)
+ {
+ SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
+ };
+
+ client.OnReady += onReady;
+
+ // safety measure for now, until we performance test / improve backoff for failed connections.
+ client.OnConnectionFailed += (_, __) => client.Deinitialize();
+
+ client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+
+ (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
+ {
+ status.UnbindBindings();
+ status.BindTo(u.NewValue.Status);
+
+ activity.UnbindBindings();
+ activity.BindTo(u.NewValue.Activity);
+ }, true);
+
+ ruleset.BindValueChanged(_ => updateStatus());
+ status.BindValueChanged(_ => updateStatus());
+ activity.BindValueChanged(_ => updateStatus());
+
+ client.Initialize();
+ }
+
+ private void onReady(object _, ReadyMessage __)
+ {
+ Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
+ updateStatus();
+ }
+
+ private void updateStatus()
+ {
+ if (!client.IsInitialized)
+ return;
+
+ if (status.Value is UserStatusOffline)
+ {
+ client.ClearPresence();
+ return;
+ }
+
+ if (status.Value is UserStatusOnline && activity.Value != null)
+ {
+ presence.State = truncate(activity.Value.Status);
+ presence.Details = truncate(getDetails(activity.Value));
+ }
+ else
+ {
+ presence.State = "Idle";
+ presence.Details = string.Empty;
+ }
+
+ // update user information
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+
+ // update ruleset
+ presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
+ presence.Assets.SmallImageText = ruleset.Value.Name;
+
+ client.SetPresence(presence);
+ }
+
+ private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
+
+ private string truncate(string str)
+ {
+ if (Encoding.UTF8.GetByteCount(str) <= 128)
+ return str;
+
+ ReadOnlyMemory strMem = str.AsMemory();
+
+ do
+ {
+ strMem = strMem[..^1];
+ } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
+
+ return string.Create(strMem.Length + 1, strMem, (span, mem) =>
+ {
+ mem.Span.CopyTo(span);
+ span[^1] = '…';
+ });
+ }
+
+ private string getDetails(UserActivity activity)
+ {
+ switch (activity)
+ {
+ case UserActivity.SoloGame solo:
+ return solo.Beatmap.ToString();
+
+ case UserActivity.Editing edit:
+ return edit.Beatmap.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ client.Dispose();
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 93fd3935c6..f70cc24159 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
@@ -7,20 +7,24 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using osu.Desktop.Overlays;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Game;
using osuTK.Input;
using Microsoft.Win32;
using osu.Desktop.Updater;
using osu.Framework;
+using osu.Framework.Logging;
using osu.Framework.Platform.Windows;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+using osu.Game.Updater;
namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
private readonly bool noVersionOverlay;
+ private VersionManager versionManager;
public OsuGameDesktop(string[] args = null)
: base(args)
@@ -32,12 +36,15 @@ namespace osu.Desktop
{
try
{
- return new StableStorage();
+ if (Host is DesktopGameHost desktopHost)
+ return new StableStorage(desktopHost);
}
- catch
+ catch (Exception)
{
- return null;
+ Logger.Log("Could not find a stable install", LoggingTarget.Runtime, LogLevel.Important);
}
+
+ return null;
}
protected override void LoadComplete()
@@ -46,24 +53,39 @@ namespace osu.Desktop
if (!noVersionOverlay)
{
- LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
- {
- Add(v);
- v.State = Visibility.Visible;
- });
+ LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
Add(new SquirrelUpdateManager());
else
Add(new SimpleUpdateManager());
}
+
+ LoadComponentAsync(new DiscordRichPresence(), Add);
+ }
+
+ protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
+ {
+ base.ScreenChanged(lastScreen, newScreen);
+
+ switch (newScreen)
+ {
+ case IntroScreen _:
+ case MainMenu _:
+ versionManager?.Show();
+ break;
+
+ default:
+ versionManager?.Hide();
+ break;
+ }
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
- var desktopWindow = host.Window as DesktopGameWindow;
- if (desktopWindow != null)
+
+ if (host.Window is DesktopGameWindow desktopWindow)
{
desktopWindow.CursorState |= CursorState.Hidden;
@@ -76,7 +98,7 @@ namespace osu.Desktop
private void fileDrop(object sender, FileDropEventArgs e)
{
- var filePaths = new[] { e.FileName };
+ var filePaths = e.FileNames;
var firstExtension = Path.GetExtension(filePaths.First());
@@ -92,14 +114,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;
@@ -119,8 +141,8 @@ namespace osu.Desktop
return null;
}
- public StableStorage()
- : base(string.Empty, null)
+ public StableStorage(DesktopGameHost host)
+ : base(string.Empty, host)
{
}
}
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index f31bba1e1e..8c759f8487 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -1,40 +1,25 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
-using System;
using osu.Framework.Allocation;
+using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Platform;
using osu.Game;
-using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Overlays;
-using osu.Game.Overlays.Notifications;
-using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
namespace osu.Desktop.Overlays
{
- public class VersionManager : OverlayContainer
+ public class VersionManager : VisibilityContainer
{
- private OsuConfigManager config;
- private OsuGameBase game;
- private NotificationOverlay notificationOverlay;
- private GameHost host;
-
[BackgroundDependencyLoader]
- private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
+ private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
{
- notificationOverlay = notification;
- this.config = config;
- this.game = game;
- this.host = host;
-
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
@@ -60,12 +45,12 @@ namespace osu.Desktop.Overlays
{
new OsuSpriteText
{
- Font = @"Exo2.0-Bold",
+ Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = game.Name
},
new OsuSpriteText
{
- Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
+ Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
Text = game.Version
},
}
@@ -74,9 +59,8 @@ namespace osu.Desktop.Overlays
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- TextSize = 12,
+ Font = OsuFont.Numeric.With(size: 12),
Colour = colours.Yellow,
- Font = @"Venera",
Text = @"Development Build"
},
new Sprite
@@ -90,49 +74,14 @@ namespace osu.Desktop.Overlays
};
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- var version = game.Version;
- var lastVersion = config.Get(OsuSetting.Version);
- if (game.IsDeployedBuild && version != lastVersion)
- {
- config.Set(OsuSetting.Version, version);
-
- // only show a notification if we've previously saved a version to the config file (ie. not the first run).
- if (!string.IsNullOrEmpty(lastVersion))
- notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
- }
- }
-
- private class UpdateCompleteNotification : SimpleNotification
- {
- public UpdateCompleteNotification(string version, Action openUrl = null)
- {
- Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
- Icon = FontAwesome.fa_check_square;
- Activated = delegate
- {
- openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
- return true;
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- IconBackgound.Colour = colours.BlueDark;
- }
- }
-
protected override void PopIn()
{
- this.FadeIn(1000);
+ this.FadeIn(1400, Easing.OutQuint);
}
protected override void PopOut()
{
+ this.FadeOut(500, Easing.OutQuint);
}
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 257155478f..bd91bcc933 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
@@ -11,6 +11,7 @@ using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.IPC;
+using osu.Game.Tournament;
namespace osu.Desktop
{
@@ -21,31 +22,44 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
+ bool useSdl = args.Contains("--sdl");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
{
host.ExceptionThrown += handleException;
if (!host.IsPrimaryInstance)
{
- var importer = new ArchiveImportIPCChannel(host);
- // Restore the cwd so relative paths given at the command line work correctly
- Directory.SetCurrentDirectory(cwd);
- foreach (var file in args)
+ if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
{
- Console.WriteLine(@"Importing {0}", file);
- if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
- throw new TimeoutException(@"IPC took too long to send");
+ var importer = new ArchiveImportIPCChannel(host);
+ // Restore the cwd so relative paths given at the command line work correctly
+ Directory.SetCurrentDirectory(cwd);
+
+ foreach (var file in args)
+ {
+ Console.WriteLine(@"Importing {0}", file);
+ if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
+ throw new TimeoutException(@"IPC took too long to send");
+ }
+
+ return 0;
}
+
+ // we want to allow multiple instances to be started when in debug.
+ if (!DebugUtils.IsDebugBuild)
+ return 0;
}
- else
+
+ switch (args.FirstOrDefault() ?? string.Empty)
{
- switch (args.FirstOrDefault() ?? string.Empty)
- {
- default:
- host.Run(new OsuGameDesktop(args));
- break;
- }
+ default:
+ host.Run(new OsuGameDesktop(args));
+ break;
+
+ case "--tournament":
+ host.Run(new TournamentGame());
+ break;
}
return 0;
diff --git a/osu.Desktop/Properties/launchSettings.json b/osu.Desktop/Properties/launchSettings.json
new file mode 100644
index 0000000000..5e768ec9fa
--- /dev/null
+++ b/osu.Desktop/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "osu! Desktop": {
+ "commandName": "Project"
+ },
+ "osu! Tournament": {
+ "commandName": "Project",
+ "commandLineArgs": "--tournament"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 19383d617f..60b47a8b3a 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -1,14 +1,13 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
-using System.IO;
-using System.Reflection;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
using osu.Game;
using osu.Game.Graphics;
@@ -21,16 +20,14 @@ using LogLevel = Splat.LogLevel;
namespace osu.Desktop.Updater
{
- public class SquirrelUpdateManager : Component
+ public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
{
private UpdateManager updateManager;
private NotificationOverlay notificationOverlay;
- public void PrepareUpdate()
- {
- // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here
- UpdateManager.RestartAppWhenExited().Wait();
- }
+ public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
+
+ private static readonly Logger logger = Logger.GetLogger("updater");
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game)
@@ -47,7 +44,7 @@ namespace osu.Desktop.Updater
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
//should we schedule a retry on completion of this check?
- bool scheduleRetry = true;
+ bool scheduleRecheck = true;
try
{
@@ -82,15 +79,16 @@ namespace osu.Desktop.Updater
{
if (useDeltaPatching)
{
- Logger.Error(e, @"delta patching failed!");
+ logger.Add(@"delta patching failed; will attempt full download!");
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas.
checkForUpdateAsync(false, notification);
- scheduleRetry = false;
+ scheduleRecheck = false;
}
else
{
+ notification.State = ProgressNotificationState.Cancelled;
Logger.Error(e, @"update failed!");
}
}
@@ -101,11 +99,8 @@ namespace osu.Desktop.Updater
}
finally
{
- if (scheduleRetry)
+ if (scheduleRecheck)
{
- if (notification != null)
- notification.State = ProgressNotificationState.Cancelled;
-
//check again in 30 minutes.
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
}
@@ -135,8 +130,8 @@ namespace osu.Desktop.Updater
Text = @"Update ready to install. Click to restart!",
Activated = () =>
{
- updateManager.PrepareUpdate();
- game.GracefullyExit();
+ updateManager.PrepareUpdateAsync()
+ .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
return true;
}
};
@@ -158,7 +153,7 @@ namespace osu.Desktop.Updater
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Icon = FontAwesome.fa_upload,
+ Icon = FontAwesome.Solid.Upload,
Colour = Color4.White,
Size = new Vector2(20),
}
@@ -168,23 +163,14 @@ namespace osu.Desktop.Updater
private class SquirrelLogger : Splat.ILogger, IDisposable
{
- private readonly string path;
- private readonly object locker = new object();
public LogLevel Level { get; set; } = LogLevel.Info;
- public SquirrelLogger()
- {
- var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log");
- if (File.Exists(file)) File.Delete(file);
- path = file;
- }
-
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level)
return;
- lock (locker) File.AppendAllText(path, message + "\r\n");
+ logger.Add(message);
}
public void Dispose()
diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest
new file mode 100644
index 0000000000..2e9127bf44
--- /dev/null
+++ b/osu.Desktop/app.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico
old mode 100644
new mode 100755
index 0c894dca41..a6aa8abb9f
Binary files a/osu.Desktop/lazer.ico and b/osu.Desktop/lazer.ico differ
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index ad08f57c3a..da47ad8223 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,15 +1,14 @@
-
- netcoreapp2.2
+ netcoreapp3.1
WinExe
- AnyCPU
true
click the circles. to the beat.
osu!
osu!lazer
osu!lazer
lazer.ico
+ app.manifest
0.0.0
0.0.0
@@ -17,19 +16,20 @@
osu.Desktop.Program
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index cdd232a9b2..a919d54f38 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -12,7 +12,7 @@
click the circles. to the beat.
click the circles.
testing
- Copyright ppy Pty Ltd 2007-2018
+ Copyright (c) 2020 ppy Pty Ltd
en-AU
diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
new file mode 100644
index 0000000000..394fd75488
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.IO.Stores;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Resources;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkBeatmapParsing : BenchmarkTest
+ {
+ private readonly MemoryStream beatmapStream = new MemoryStream();
+
+ public override void SetUp()
+ {
+ using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
+ using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
+ using (var reader = new ZipArchiveReader(archive))
+ reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
+ }
+
+ [Benchmark]
+ public Beatmap BenchmarkBundledBeatmap()
+ {
+ beatmapStream.Seek(0, SeekOrigin.Begin);
+ var reader = new LineBufferedReader(beatmapStream); // no disposal
+
+ var decoder = Decoder.GetDecoder(reader);
+ return decoder.Decode(reader);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs
new file mode 100644
index 0000000000..34f5edd084
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkTest.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 BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using NUnit.Framework;
+
+namespace osu.Game.Benchmarks
+{
+ [TestFixture]
+ [MemoryDiagnoser]
+ public abstract class BenchmarkTest
+ {
+ [GlobalSetup]
+ [OneTimeSetUp]
+ public virtual void SetUp()
+ {
+ }
+
+ [Test]
+ public void RunBenchmark() => BenchmarkRunner.Run(GetType());
+ }
+}
diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs
new file mode 100644
index 0000000000..c55075fea6
--- /dev/null
+++ b/osu.Game.Benchmarks/Program.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Running;
+
+namespace osu.Game.Benchmarks
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(args);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/Properties/launchSettings.json b/osu.Game.Benchmarks/Properties/launchSettings.json
new file mode 100644
index 0000000000..c1868088f9
--- /dev/null
+++ b/osu.Game.Benchmarks/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "All Benchmarks": {
+ "commandName": "Project",
+ "commandLineArgs": "--filter *"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
new file mode 100644
index 0000000000..f2e1c0ec3b
--- /dev/null
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp3.1
+ Exe
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..d918305f3d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Catch.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..f8c3fcd894
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
new file mode 100644
index 0000000000..88b420ffad
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Rulesets.Catch.Tests
+ osu.Game.Rulesets.Catch.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
new file mode 100644
index 0000000000..39fe3dac25
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Foundation;
+using osu.Framework.iOS;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Catch.Tests.iOS
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : GameAppDelegate
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
new file mode 100644
index 0000000000..f7f07ef938
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using UIKit;
+
+namespace osu.Game.Rulesets.Catch.Tests.iOS
+{
+ public static class Application
+ {
+ public static void Main(string[] args)
+ {
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist
new file mode 100644
index 0000000000..9ae599370b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
new file mode 100644
index 0000000000..5115746cbb
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CFBundleName
+ osu.Game.Rulesets.Catch.Tests.iOS
+ CFBundleIdentifier
+ ppy.osu-Game-Rulesets-Catch-Tests-iOS
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ 10.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
new file mode 100644
index 0000000000..be6044bbd0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -0,0 +1,35 @@
+
+
+
+ Debug
+ iPhoneSimulator
+ {4004C7B7-1A62-43F1-9DF2-52450FA67E70}
+ Exe
+ osu.Game.Rulesets.Catch.Tests
+ osu.Game.Rulesets.Catch.Tests.iOS
+
+
+
+
+
+
+ Linker.xml
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}
+ osu.Game.Rulesets.Catch
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
index da9344b6a2..67d27c33eb 100644
--- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 162624da57..f4749be370 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -1,11 +1,12 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -21,10 +22,11 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("basic")]
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ [TestCase("slider")]
+ [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
+ public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
@@ -33,13 +35,18 @@ namespace osu.Game.Rulesets.Catch.Tests
case JuiceStream stream:
foreach (var nested in stream.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
+
break;
+
case BananaShower shower:
foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
+
break;
+
default:
yield return new ConvertValue((CatchHitObject)hitObject);
+
break;
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
new file mode 100644
index 0000000000..51fe0b035d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
+
+ [TestCase(4.2058561036909863d, "diffcalc-test")]
+ public void Test(double expected, string name)
+ => base.Test(expected, name);
+
+ protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
+
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
new file mode 100644
index 0000000000..04e6dea376
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class CatchLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
deleted file mode 100644
index a2c886f1f3..0000000000
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using NUnit.Framework;
-
-namespace osu.Game.Rulesets.Catch.Tests
-{
- [TestFixture]
- public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
- {
- public TestCaseCatchPlayer() : base(new CatchRuleset())
- {
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
deleted file mode 100644
index 14487b2c7f..0000000000
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using NUnit.Framework;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
-
-namespace osu.Game.Rulesets.Catch.Tests
-{
- [TestFixture]
- public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer
- {
- public TestCaseHyperDash()
- : base(new CatchRuleset())
- {
- }
-
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
- {
- var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
-
-
- for (int i = 0; i < 512; i++)
- if (i % 5 < 3)
- beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
-
- return beatmap;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
similarity index 74%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index fc3809fae4..74a9c05bf9 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Game.Beatmaps;
@@ -13,21 +13,21 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestCaseAutoJuiceStream : TestCasePlayer
+ public class TestSceneAutoJuiceStream : PlayerTestScene
{
- public TestCaseAutoJuiceStream()
+ public TestSceneAutoJuiceStream()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
similarity index 68%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index b5cf0e3d1d..0ad72412fc 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
@@ -8,11 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer
+ public class TestSceneBananaShower : PlayerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -20,22 +21,22 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableBananaShower),
typeof(CatchRuleset),
- typeof(CatchRulesetContainer),
+ typeof(DrawableCatchRuleset),
};
- public TestCaseBananaShower()
+ public TestSceneBananaShower()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
new file mode 100644
index 0000000000..9836a7811a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatchPlayer : PlayerTestScene
+ {
+ public TestSceneCatchPlayer()
+ : base(new CatchRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
similarity index 63%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 8a90b48180..9ce46ad6ba 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -1,32 +1,32 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
+ public class TestSceneCatchStacker : PlayerTestScene
{
- public TestCaseCatchStacker()
+ public TestSceneCatchStacker()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
-
for (int i = 0; i < 512; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
new file mode 100644
index 0000000000..9b529a2e4c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -0,0 +1,106 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
+using System;
+using System.Collections.Generic;
+using osu.Game.Skinning;
+using osu.Framework.Graphics.Shapes;
+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
+{
+ [TestFixture]
+ public class TestSceneCatcher : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherSprite),
+ };
+
+ private readonly Container container;
+
+ public TestSceneCatcher()
+ {
+ Child = container = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddStep("show default catcher implementation", () => { container.Child = new CatcherSprite(); });
+
+ AddStep("show custom catcher implementation", () =>
+ {
+ container.Child = new CatchCustomSkinSourceContainer
+ {
+ Child = new CatcherSprite()
+ };
+ });
+ }
+
+ private class CatcherCustomSkin : Container
+ {
+ public CatcherCustomSkin()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Blue
+ },
+ new OsuSpriteText
+ {
+ Text = "custom"
+ }
+ };
+ }
+ }
+
+ [Cached(typeof(ISkinSource))]
+ private class CatchCustomSkinSourceContainer : Container, ISkinSource
+ {
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component.LookupName)
+ {
+ case "Gameplay/catch/fruit-catcher-idle":
+ return new CatcherCustomSkin();
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) =>
+ throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName) =>
+ throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
similarity index 86%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 25f7ca108d..3ae6886c31 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
@@ -13,7 +13,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatcherArea : OsuTestCase
+ public class TestSceneCatcherArea : OsuTestScene
{
private RulesetInfo catchRuleset;
private TestCatcherArea catcherArea;
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(CatcherArea),
};
- public TestCaseCatcherArea()
+ public TestSceneCatcherArea()
{
AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
new file mode 100644
index 0000000000..1eb913e900
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -0,0 +1,160 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneDrawableHitObjects : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea.Catcher),
+ typeof(DrawableCatchRuleset),
+ typeof(DrawableFruit),
+ typeof(DrawableJuiceStream),
+ typeof(DrawableBanana)
+ };
+
+ private DrawableCatchRuleset drawableRuleset;
+ private double playfieldTime => drawableRuleset.Playfield.Time.Current;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new TimingControlPoint());
+
+ WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects = new List { new Fruit() },
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty(),
+ Metadata = new BeatmapMetadata
+ {
+ Artist = @"Unknown",
+ Title = @"You're breathtaking",
+ AuthorString = @"Everyone",
+ },
+ Ruleset = new CatchRuleset().RulesetInfo
+ },
+ ControlPointInfo = controlPointInfo
+ });
+
+ Add(new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo))
+ }
+ });
+
+ AddStep("miss fruits", () => spawnFruits());
+ AddStep("hit fruits", () => spawnFruits(true));
+ AddStep("miss juicestream", () => spawnJuiceStream());
+ AddStep("hit juicestream", () => spawnJuiceStream(true));
+ AddStep("miss bananas", () => spawnBananas());
+ AddStep("hit bananas", () => spawnBananas(true));
+ }
+
+ private void spawnFruits(bool hit = false)
+ {
+ for (int i = 1; i <= 4; i++)
+ {
+ var fruit = new Fruit
+ {
+ X = getXCoords(hit),
+ LastInCombo = i % 4 == 0,
+ StartTime = playfieldTime + 800 + (200 * i)
+ };
+
+ fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ addToPlayfield(new DrawableFruit(fruit));
+ }
+ }
+
+ private void spawnJuiceStream(bool hit = false)
+ {
+ var xCoords = getXCoords(hit);
+
+ var juice = new JuiceStream
+ {
+ X = xCoords,
+ StartTime = playfieldTime + 1000,
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(0, 200)
+ })
+ };
+
+ juice.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ if (juice.NestedHitObjects.Last() is CatchHitObject tail)
+ tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
+
+ addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
+ }
+
+ private void spawnBananas(bool hit = false)
+ {
+ for (int i = 1; i <= 4; i++)
+ {
+ var banana = new Banana
+ {
+ X = getXCoords(hit),
+ LastInCombo = i % 4 == 0,
+ StartTime = playfieldTime + 800 + (200 * i)
+ };
+
+ banana.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ addToPlayfield(new DrawableBanana(banana));
+ }
+ }
+
+ private float getXCoords(bool hit)
+ {
+ const float x_offset = 0.2f;
+ float xCoords = drawableRuleset.Playfield.Width / 2;
+
+ if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
+ catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
+
+ if (hit)
+ xCoords -= x_offset;
+ else
+ xCoords += x_offset;
+
+ return xCoords;
+ }
+
+ private void addToPlayfield(DrawableCatchHitObject drawable)
+ {
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObjects(new[] { drawable });
+
+ drawableRuleset.Playfield.Add(drawable);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
new file mode 100644
index 0000000000..8c3dfef39c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Rulesets.Catch.Mods;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
+ {
+ public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ SelectedMods.Value = new[] { new CatchModHidden() };
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
similarity index 90%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index 7b6773ad58..44517382f7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
@@ -15,7 +15,7 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseFruitObjects : OsuTestCase
+ public class TestSceneFruitObjects : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(Pulp),
};
- public TestCaseFruitObjects()
+ public TestSceneFruitObjects()
{
Add(new GridContainer
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
new file mode 100644
index 0000000000..da36673930
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -0,0 +1,51 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneHyperDash : PlayerTestScene
+ {
+ public TestSceneHyperDash()
+ : base(new CatchRuleset())
+ {
+ }
+
+ protected override bool Autoplay => true;
+
+ [Test]
+ public void TestHyperDash()
+ {
+ AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = ruleset,
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
+ }
+ };
+
+ // Should produce a hyper-dash
+ beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true });
+ beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
+
+ for (int i = 0; i < 512; i++)
+ {
+ if (i % 5 < 3)
+ beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 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 e875af5a30..9559d13328 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,14 +2,14 @@
-
-
-
+
+
+
WinExe
- netcoreapp2.2
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
index 5b4af6ea8a..18cc300ff9 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -1,10 +1,10 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
@@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
Name = @"Fruit Count",
Content = fruits.ToString(),
- Icon = FontAwesome.fa_circle_o
+ Icon = FontAwesome.Regular.Circle
},
new BeatmapStatistic
{
Name = @"Juice Stream Count",
Content = juiceStreams.ToString(),
- Icon = FontAwesome.fa_circle
+ Icon = FontAwesome.Regular.Circle
},
new BeatmapStatistic
{
Name = @"Banana Shower Count",
Content = bananaShowers.ToString(),
- Icon = FontAwesome.fa_circle
+ Icon = FontAwesome.Regular.Circle
}
};
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index c3dc9499c2..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -1,69 +1,66 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
-using System;
+using System.Linq;
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
{
public class CatchBeatmapConverter : BeatmapConverter
{
- public CatchBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public CatchBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
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 22c1180c09..db52fbac1b 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
@@ -8,8 +8,8 @@ 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;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -26,11 +26,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
base.PostProcess();
- applyPositionOffsets();
+ ApplyPositionOffsets(Beatmap);
initialiseHyperDash((List)Beatmap.HitObjects);
int index = 0;
+
foreach (var obj in Beatmap.HitObjects.OfType())
{
obj.IndexInBeatmap = index++;
@@ -39,39 +40,152 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- private void applyPositionOffsets()
+ public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods)
{
var rng = new FastRandom(RNG_SEED);
- // todo: HardRock displacement should be applied here
- foreach (var obj in Beatmap.HitObjects)
+ bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock);
+ float? lastPosition = null;
+ double lastStartTime = 0;
+
+ foreach (var obj in beatmap.HitObjects.OfType())
{
+ obj.XOffset = 0;
+
switch (obj)
{
+ case Fruit fruit:
+ if (shouldApplyHardRockOffset)
+ applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
+ break;
+
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType())
{
- banana.X = (float)rng.NextDouble();
+ banana.XOffset = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
}
+
break;
+
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
- var hitObject = (CatchHitObject)nested;
- if (hitObject is TinyDroplet)
- hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
- else if (hitObject is Droplet)
+ var catchObject = (CatchHitObject)nested;
+ catchObject.XOffset = 0;
+
+ if (catchObject is TinyDroplet)
+ 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
- hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
+
break;
}
}
}
+ private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
+ {
+ if (hitObject is JuiceStream stream)
+ {
+ lastPosition = stream.EndX;
+ lastStartTime = stream.EndTime;
+ return;
+ }
+
+ if (!(hitObject is Fruit))
+ return;
+
+ float offsetPosition = hitObject.X;
+ double startTime = hitObject.StartTime;
+
+ if (lastPosition == null)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+
+ return;
+ }
+
+ float positionDiff = offsetPosition - lastPosition.Value;
+ double timeDiff = startTime - lastStartTime;
+
+ if (timeDiff > 1000)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ return;
+ }
+
+ if (positionDiff == 0)
+ {
+ applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
+ hitObject.XOffset = offsetPosition - hitObject.X;
+ return;
+ }
+
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ applyOffset(ref offsetPosition, positionDiff);
+
+ hitObject.XOffset = offsetPosition - hitObject.X;
+
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ }
+
+ ///
+ /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The maximum offset, cannot exceed 20px.
+ /// The random number generator.
+ private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
+ {
+ bool right = rng.NextBool();
+ float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
+
+ if (right)
+ {
+ // Clamp to the right bound
+ if (position + rand <= 1)
+ position += rand;
+ else
+ position -= rand;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position - rand >= 0)
+ position -= rand;
+ else
+ position += rand;
+ }
+ }
+
+ ///
+ /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The amount to offset by.
+ private static void applyOffset(ref float position, float amount)
+ {
+ if (amount > 0)
+ {
+ // Clamp to the right bound
+ if (position + amount < 1)
+ position += amount;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position + amount > 0)
+ position += amount;
+ }
+ }
+
private void initialiseHyperDash(List objects)
{
List objectWithDroplets = new List();
@@ -80,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));
@@ -98,9 +217,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
- double timeToNext = nextObject.StartTime - currentObject.StartTime;
+ double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
+
if (distanceToHyper < 0)
{
currentObject.HyperDashTarget = nextObject;
@@ -109,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/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs
index 4f976bb7b2..021d7a7efe 100644
--- a/osu.Game.Rulesets.Catch/CatchInputManager.cs
+++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Input.Bindings;
@@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
{
[Description("Move left")]
MoveLeft,
+
[Description("Move right")]
MoveRight,
+
[Description("Engage dash")]
Dash,
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 1f1d2475f6..e5c3647f99 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Beatmaps;
using osu.Game.Graphics;
@@ -9,22 +9,33 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using System;
namespace osu.Game.Rulesets.Catch
{
- public class CatchRuleset : Ruleset
+ public class CatchRuleset : Ruleset, ILegacyRuleset
{
- public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "fruits";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
@@ -42,7 +53,14 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new CatchModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new CatchModSuddenDeath();
+
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new CatchModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -63,14 +81,8 @@ namespace osu.Game.Rulesets.Catch
if (mods.HasFlag(LegacyMods.NoFail))
yield return new CatchModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new CatchModPerfect();
-
if (mods.HasFlag(LegacyMods.Relax))
yield return new CatchModRelax();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new CatchModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
@@ -84,6 +96,7 @@ namespace osu.Game.Rulesets.Catch
new CatchModNoFail(),
new MultiMod(new CatchModHalfTime(), new CatchModDaycore())
};
+
case ModType.DifficultyIncrease:
return new Mod[]
{
@@ -93,32 +106,45 @@ namespace osu.Game.Rulesets.Catch
new CatchModHidden(),
new CatchModFlashlight(),
};
+
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new CatchModAutoplay(), new ModCinema()),
+ new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
new CatchModRelax(),
};
+
+ case ModType.Fun:
+ return new Mod[]
+ {
+ new MultiMod(new ModWindUp(), new ModWindDown())
+ };
+
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
public override string Description => "osu!catch";
- public override string ShortName => "fruits";
+ public override string ShortName => SHORT_NAME;
- public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
+ public override string PlayingVerb => "Catching fruit";
+
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
- public override int? LegacyID => 2;
+ public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
+
+ public int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
-
- public CatchRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
new file mode 100644
index 0000000000..8bf53e53e3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch
+{
+ public class CatchSkinComponent : GameplaySkinComponent
+ {
+ public CatchSkinComponent(CatchSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
new file mode 100644
index 0000000000..7e482d4045
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -0,0 +1,9 @@
+// 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.Catch
+{
+ public enum CatchSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
index f6535380c8..75f5b18607 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
@@ -1,8 +1,7 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
public double ApproachRate;
public int MaxCombo;
-
- public CatchDifficultyAttributes(Mod[] mods, double starRating)
- : base(mods, starRating)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index a763989750..44e1a8e5cc 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,149 +1,91 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Catch.Difficulty.Skills;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
-
- ///
- /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
- /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
- /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
- ///
- private const double strain_step = 750;
-
- ///
- /// The weighting of each strain value decays to this number * it's previous value
- ///
- private const double decay_weight = 0.94;
-
private const double star_scaling_factor = 0.145;
+ protected override int SectionLength => 750;
+
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
- if (!beatmap.HitObjects.Any())
- return new CatchDifficultyAttributes(mods, 0);
-
- var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
- float halfCatchWidth = catcher.CatchWidth * 0.5f;
-
- var difficultyHitObjects = new List();
-
- foreach (var hitObject in beatmap.HitObjects)
- {
- switch (hitObject)
- {
- // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
- case Fruit fruit:
- difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth));
- break;
- case JuiceStream _:
- difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
- break;
- }
- }
-
- difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
-
- if (!calculateStrainValues(difficultyHitObjects, timeRate))
- return new CatchDifficultyAttributes(mods, 0);
+ if (beatmap.HitObjects.Count == 0)
+ return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
// this is the same as osu!, so there's potential to share the implementation... maybe
- double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
- double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
+ double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
- return new CatchDifficultyAttributes(mods, starRating)
+ return new CatchDifficultyAttributes
{
+ StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
+ Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
- MaxCombo = difficultyHitObjects.Count
+ MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
+ Skills = skills
};
}
- private bool calculateStrainValues(List objects, double timeRate)
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
- CatchDifficultyHitObject lastObject = null;
+ float halfCatchWidth;
- if (!objects.Any()) return false;
-
- // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
- foreach (var currentObject in objects)
+ using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty))
{
+ halfCatchWidth = catcher.CatchWidth * 0.5f;
+ halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
+ }
+
+ CatchHitObject lastObject = null;
+
+ // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
+ foreach (var hitObject in beatmap.HitObjects
+ .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj })
+ .Cast()
+ .OrderBy(x => x.StartTime))
+ {
+ // We want to only consider fruits that contribute to the combo.
+ if (hitObject is BananaShower || hitObject is TinyDroplet)
+ continue;
+
if (lastObject != null)
- currentObject.CalculateStrains(lastObject, timeRate);
+ yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth);
- lastObject = currentObject;
+ lastObject = hitObject;
}
-
- return true;
}
- private double calculateDifficulty(List objects, double timeRate)
+ protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
{
- // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
- double actualStrainStep = strain_step * timeRate;
+ new Movement(),
+ };
- // Find the highest strain value within each strain step
- var highestStrains = new List();
- double intervalEndTime = actualStrainStep;
- double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
-
- CatchDifficultyHitObject previousHitObject = null;
- foreach (CatchDifficultyHitObject hitObject in objects)
- {
- // While we are beyond the current interval push the currently available maximum to our strain list
- while (hitObject.BaseHitObject.StartTime > intervalEndTime)
- {
- highestStrains.Add(maximumStrain);
-
- // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
- // until the beginning of the next interval.
- if (previousHitObject == null)
- {
- maximumStrain = 0;
- }
- else
- {
- double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
- maximumStrain = previousHitObject.Strain * decay;
- }
-
- // Go to the next time interval
- intervalEndTime += actualStrainStep;
- }
-
- // Obtain maximum strain
- maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
-
- previousHitObject = hitObject;
- }
-
- // Build the weighted sum over the highest strains for each interval
- double difficulty = 0;
- double weight = 1;
- highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
-
- foreach (double strain in highestStrains)
- {
- difficulty += weight * strain;
- weight *= decay_weight;
- }
-
- return difficulty;
- }
+ protected override Mod[] DifficultyAdjustmentMods => new Mod[]
+ {
+ new CatchModDoubleTime(),
+ new CatchModHalfTime(),
+ new CatchModHardRock(),
+ new CatchModEasy(),
+ };
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
deleted file mode 100644
index bd7796ed91..0000000000
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Difficulty
-{
- public class CatchDifficultyHitObject
- {
- internal static readonly double DECAY_BASE = 0.20;
- private const float normalized_hitobject_radius = 41.0f;
- private const float absolute_player_positioning_error = 16f;
- private readonly float playerPositioningError;
-
- internal CatchHitObject BaseHitObject;
-
- ///
- /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
- ///
- internal double Strain = 1;
-
- ///
- /// This is required to keep track of lazy player movement (always moving only as far as necessary)
- /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
- ///
- internal float PlayerPositionOffset;
- internal float LastMovement;
-
- internal float NormalizedPosition;
- internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
-
- internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
- {
- BaseHitObject = baseHitObject;
-
- // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
- float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
-
- playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
- NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
- }
-
- private const double direction_change_bonus = 12.5;
- internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
- {
- // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
- // See Taiko feedback thread.
- double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
- double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
-
- // Update new position with lazy movement.
- PlayerPositionOffset =
- MathHelper.Clamp(
- previousHitObject.ActualNormalizedPosition,
- NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
- NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
- - NormalizedPosition; // Subtract HitObject position to obtain offset
-
- LastMovement = DistanceTo(previousHitObject);
- double addition = spacingWeight(LastMovement);
-
- if (NormalizedPosition < previousHitObject.NormalizedPosition)
- {
- LastMovement = -LastMovement;
- }
-
- CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
-
- double additionBonus = 0;
- double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
-
- // Direction changes give an extra point!
- if (Math.Abs(LastMovement) > 0.1)
- {
- if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
- {
- double bonus = direction_change_bonus / sqrtTime;
-
- // Weight bonus by how
- double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
-
- // We want time to play a role twice here!
- addition += bonus * bonusFactor;
-
- // Bonus for tougher direction switches and "almost" hyperdashes at this point
- if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
- {
- additionBonus += 0.3 * bonusFactor;
- }
- }
-
- // Base bonus for every movement, giving some weight to streams.
- addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
- }
-
- // Bonus for "almost" hyperdashes at corner points
- if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
- {
- if (!previousHitCircle.HyperDash)
- {
- additionBonus += 1.0;
- }
- else
- {
- // After a hyperdash we ARE in the correct position. Always!
- PlayerPositionOffset = 0;
- }
-
- addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
- }
-
- addition *= 850.0 / Math.Max(timeElapsed, 25);
-
- Strain = previousHitObject.Strain * decay + addition;
- }
-
- private static double spacingWeight(float distance)
- {
- return Math.Pow(distance, 1.3) / 500;
- }
-
- internal float DistanceTo(CatchDifficultyHitObject other)
- {
- return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
new file mode 100644
index 0000000000..a6283eb7c4
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -0,0 +1,99 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Scoring.Legacy;
+
+namespace osu.Game.Rulesets.Catch.Difficulty
+{
+ public class CatchPerformanceCalculator : PerformanceCalculator
+ {
+ protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes;
+
+ private Mod[] mods;
+
+ private int fruitsHit;
+ private int ticksHit;
+ private int tinyTicksHit;
+ private int tinyTicksMissed;
+ private int misses;
+
+ public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
+ : base(ruleset, beatmap, score)
+ {
+ }
+
+ public override double Calculate(Dictionary categoryDifficulty = null)
+ {
+ mods = Score.Mods;
+
+ fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
+ ticksHit = Score?.GetCount100() ?? 0;
+ tinyTicksHit = Score?.GetCount50() ?? 0;
+ tinyTicksMissed = Score?.GetCountKatu() ?? 0;
+ misses = Score.Statistics[HitResult.Miss];
+
+ // Don't count scores made with supposedly unranked mods
+ if (mods.Any(m => !m.Ranked))
+ return 0;
+
+ // We are heavily relying on aim in catch the beat
+ 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
+ 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.97, misses);
+
+ // Combo scaling
+ if (Attributes.MaxCombo > 0)
+ value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+
+ 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.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.35 * lengthBonus;
+
+ // Scale the aim value with accuracy _slightly_
+ value *= Math.Pow(accuracy(), 5.5);
+
+ // Custom multipliers for NoFail. SpunOut is not applicable.
+ if (mods.Any(m => m is ModNoFail))
+ value *= 0.90;
+
+ return value;
+ }
+
+ 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/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
new file mode 100644
index 0000000000..24e526ed19
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
+{
+ public class CatchDifficultyHitObject : DifficultyHitObject
+ {
+ private const float normalized_hitobject_radius = 41.0f;
+
+ public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
+
+ public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
+
+ public readonly float NormalizedPosition;
+ public readonly float LastNormalizedPosition;
+
+ ///
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms.
+ ///
+ public readonly double StrainTime;
+
+ public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
+ : base(hitObject, lastObject, clockRate)
+ {
+ // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
+ var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
+
+ NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
+ LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
+
+ // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
+ StrainTime = Math.Max(25, DeltaTime);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
new file mode 100644
index 0000000000..7cd569035b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Skills;
+
+namespace osu.Game.Rulesets.Catch.Difficulty.Skills
+{
+ public class Movement : Skill
+ {
+ private const float absolute_player_positioning_error = 16f;
+ private const float normalized_hitobject_radius = 41.0f;
+ private const double direction_change_bonus = 12.5;
+
+ protected override double SkillMultiplier => 850;
+ protected override double StrainDecayBase => 0.2;
+
+ protected override double DecayWeight => 0.94;
+
+ private float? lastPlayerPosition;
+ private float lastDistanceMoved;
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ var catchCurrent = (CatchDifficultyHitObject)current;
+
+ if (lastPlayerPosition == null)
+ lastPlayerPosition = catchCurrent.LastNormalizedPosition;
+
+ float playerPosition = Math.Clamp(
+ lastPlayerPosition.Value,
+ catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
+ catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
+ );
+
+ float distanceMoved = playerPosition - lastPlayerPosition.Value;
+
+ double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
+ double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
+
+ double bonus = 0;
+
+ // Direction changes give an extra point!
+ if (Math.Abs(distanceMoved) > 0.1)
+ {
+ if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
+ {
+ double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
+
+ distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
+
+ // Bonus for tougher direction switches and "almost" hyperdashes at this point
+ if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
+ bonus = 0.3 * bonusFactor;
+ }
+
+ // Base bonus for every movement, giving some weight to streams.
+ distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
+ }
+
+ // Bonus for "almost" hyperdashes at corner points
+ if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
+ {
+ if (!catchCurrent.LastObject.HyperDash)
+ bonus += 1.0;
+ else
+ {
+ // After a hyperdash we ARE in the correct position. Always!
+ playerPosition = catchCurrent.NormalizedPosition;
+ }
+
+ distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
+ }
+
+ lastPlayerPosition = playerPosition;
+ lastDistanceMoved = distanceMoved;
+
+ return distanceAddition / catchCurrent.StrainTime;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
index d89d987f95..fc030877f1 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
return 1100;
}
@@ -27,8 +28,9 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
- return 8;
+ return 0.01;
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
index 1fbf1db7f7..e87ecba749 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Scoring;
@@ -13,20 +13,10 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
return 30;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return 0;
- case HitResult.Perfect:
- return 7;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index b20bc43886..2149ed9712 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
@@ -17,22 +17,12 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
return 300;
}
}
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return 0;
- case HitResult.Perfect:
- return 10.2;
- }
- }
-
///
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
index fc933020d3..d607b49ea4 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Scoring;
@@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
return 10;
}
@@ -26,8 +27,9 @@ namespace osu.Game.Rulesets.Catch.Judgements
{
default:
return 0;
+
case HitResult.Perfect:
- return 4;
+ return 0.02;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index 5b3835755a..46e427e1b7 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
@@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
- private const uint y = 842502087;
- private const uint z = 3579807591;
- private const uint w = 273326509;
- private uint _x, _y = y, _z = z, _w = w;
+ private const uint y_initial = 842502087;
+ private const uint z_initial = 3579807591;
+ private const uint w_initial = 273326509;
+ private uint x, y = y_initial, z = z_initial, w = w_initial;
public FastRandom(int seed)
{
- _x = (uint)seed;
+ x = (uint)seed;
}
public FastRandom()
@@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = _x ^ _x << 11;
- _x = _y;
- _y = _z;
- _z = _w;
- return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
+ uint t = x ^ (x << 11);
+ x = y;
+ y = z;
+ z = w;
+ return w = w ^ (w >> 19) ^ t ^ (t >> 8);
}
///
@@ -61,6 +61,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+ ///
+ /// Generates a random integer value within the range [, ).
+ ///
+ /// The lower bound of the range.
+ /// The upper bound of the range.
+ /// The random value.
+ public int Next(double lowerBound, double upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+
///
/// Generates a random double value within the range [0, 1).
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
index 8bba73ed64..692e63fa69 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModAutoplay : ModAutoplay
{
- protected override Score CreateReplayScore(Beatmap beatmap) => new Score
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
new file mode 100644
index 0000000000..3bc1ee5bf5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
@@ -0,0 +1,21 @@
+// 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.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
+ Replay = new CatchAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
index 6d4caef8d2..cae19e9468 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
new file mode 100644
index 0000000000..8377b3786a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// 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.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Fruit Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, difficulty.ApproachRate);
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
index dcf417c405..178909387f 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
index d20a2ec727..a82d0af102 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index 9cfba0236a..71268d899d 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -1,6 +1,7 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
@@ -20,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods
private CatchPlayfield playfield;
- public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- playfield = (CatchPlayfield)rulesetContainer.Playfield;
- base.ApplyToRulesetContainer(rulesetContainer);
+ playfield = (CatchPlayfield)drawableRuleset.Playfield;
+ base.ApplyToDrawableRuleset(drawableRuleset);
}
private class CatchFlashlight : Flashlight
@@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Mods
return default_flashlight_size;
}
- protected override void OnComboChange(int newCombo)
+ protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
index 4e48de454f..ce06b841aa 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 8e19c0614a..ced1900ba9 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -1,91 +1,17 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.MathUtils;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
-using System;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModHardRock : ModHardRock, IApplicableToHitObject
+ public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
{
public override double ScoreMultiplier => 1.12;
public override bool Ranked => true;
- private float lastStartX;
- private int lastStartTime;
-
- public void ApplyToHitObject(HitObject hitObject)
- {
- var catchObject = (CatchHitObject)hitObject;
-
- float position = catchObject.X;
- int startTime = (int)hitObject.StartTime;
-
- if (lastStartX == 0)
- {
- lastStartX = position;
- lastStartTime = startTime;
- return;
- }
-
- float diff = lastStartX - position;
- int timeDiff = startTime - lastStartTime;
-
- if (timeDiff > 1000)
- {
- lastStartX = position;
- lastStartTime = startTime;
- return;
- }
-
- if (diff == 0)
- {
- bool right = RNG.NextBool();
-
- float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH;
-
- if (right)
- {
- if (position + rand <= 1)
- position += rand;
- else
- position -= rand;
- }
- else
- {
- if (position - rand >= 0)
- position -= rand;
- else
- position += rand;
- }
-
- catchObject.X = position;
-
- return;
- }
-
- if (Math.Abs(diff) < timeDiff / 3d)
- {
- if (diff > 0)
- {
- if (position - diff > 0)
- position -= diff;
- }
- else
- {
- if (position - diff < 1)
- position -= diff;
- }
- }
-
- catchObject.X = position;
-
- lastStartX = position;
- lastStartTime = startTime;
- }
+ public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index f2716f351e..606a935229 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
@@ -1,7 +1,11 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Mods
{
@@ -9,5 +13,36 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override string Description => @"Play with fading fruits.";
public override double ScoreMultiplier => 1.06;
+
+ private const double fade_out_offset_multiplier = 0.6;
+ private const double fade_out_duration_multiplier = 0.44;
+
+ protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
+ {
+ if (!(drawable is DrawableCatchHitObject catchDrawable))
+ return;
+
+ if (catchDrawable.NestedHitObjects.Any())
+ {
+ foreach (var nestedDrawable in catchDrawable.NestedHitObjects)
+ {
+ if (nestedDrawable is DrawableCatchHitObject nestedCatchDrawable)
+ fadeOutHitObject(nestedCatchDrawable);
+ }
+ }
+ else
+ fadeOutHitObject(catchDrawable);
+ }
+
+ private void fadeOutHitObject(DrawableCatchHitObject drawable)
+ {
+ var hitObject = drawable.HitObject;
+
+ var offset = hitObject.TimePreempt * fade_out_offset_multiplier;
+ var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
+
+ using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true))
+ drawable.FadeOut(duration);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
index 687db172cf..c07087efaf 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
@@ -1,11 +1,12 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModNightcore : ModNightcore
+ public class CatchModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.06;
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs
index 914419438d..3c02646e99 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
index de00ff31ce..fb92399102 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 8bf9f32572..4c72b9fd3e 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -1,12 +1,51 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModRelax : ModRelax
+ public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset
{
public override string Description => @"Use the mouse to control the catcher.";
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
+ }
+
+ private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
+ {
+ private readonly CatcherArea.Catcher catcher;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ public MouseInputHelper(CatchPlayfield playfield)
+ {
+ catcher = playfield.CatcherArea.MovableCatcher;
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ //disable keyboard controls
+ public bool OnPressed(CatchAction action) => true;
+
+ public void OnReleased(CatchAction action)
+ {
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
+ return base.OnMouseMove(e);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs
index 71461cfc40..68e01391ce 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs
index e1af4c1075..0b3d1d23e0 100644
--- a/osu.Game.Rulesets.Catch/Objects/Banana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 25af7e4bdf..267e6d12c7 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
@@ -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/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 0c50dae9fb..e4ad49ea50 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -1,10 +1,13 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
@@ -12,7 +15,20 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const double OBJECT_RADIUS = 44;
- public float X { get; set; }
+ private float x;
+
+ public float X
+ {
+ get => x + XOffset;
+ set => x = value;
+ }
+
+ ///
+ /// A random offset applied to , set by the .
+ ///
+ internal float XOffset { get; set; }
+
+ public double TimePreempt = 1000;
public int IndexInBeatmap { get; set; }
@@ -22,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboOffset { get; set; }
- public int IndexInCurrentCombo { get; set; }
+ public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
- public int ComboIndex { get; set; }
+ public int IndexInCurrentCombo
+ {
+ get => IndexInCurrentComboBindable.Value;
+ set => IndexInCurrentComboBindable.Value = value;
+ }
+
+ public Bindable ComboIndexBindable { get; } = new Bindable();
+
+ public int ComboIndex
+ {
+ get => ComboIndexBindable.Value;
+ set => ComboIndexBindable.Value = value;
+ }
///
/// Difference between the distance to the next object
@@ -33,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects
///
public float DistanceToHyperDash { get; set; }
+ public Bindable LastInComboBindable { get; } = new Bindable();
+
///
/// The next fruit starts a new combo. Used for explodey.
///
- public virtual bool LastInCombo { get; set; }
+ public virtual bool LastInCombo
+ {
+ get => LastInComboBindable.Value;
+ set => LastInComboBindable.Value = value;
+ }
public float Scale { get; set; } = 1;
@@ -54,10 +88,12 @@ namespace osu.Game.Rulesets.Catch.Objects
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
+ TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
+
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
- protected override HitWindows CreateHitWindows() => null;
+ protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
public enum FruitVisualRepresentation
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
index 8756a5178f..5afdb14888 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Catch.Objects.Drawable
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
index 697fab85c9..ea415e18fa 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
@@ -1,36 +1,51 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBananaShower : DrawableCatchHitObject
{
+ private readonly Func> createDrawableRepresentation;
private readonly Container bananaContainer;
- public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null)
+ public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null)
: base(s)
{
+ this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
X = 0;
- InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
-
- foreach (var b in s.NestedHitObjects.Cast())
- AddNested(getVisualRepresentation?.Invoke(b));
+ AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
- bananaContainer.Add(h);
- base.AddNested(h);
+ base.AddNestedHitObject(hitObject);
+ bananaContainer.Add(hitObject);
+ }
+
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ bananaContainer.Clear();
+ }
+
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case Banana banana:
+ return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
+ }
+
+ return base.CreateNestedHitObject(hitObject);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 2db252ebc6..b7c05392f3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -1,14 +1,12 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using osuTK;
-using osuTK.Graphics;
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;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -52,6 +50,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public Func CheckPosition;
+ public bool IsOnPlate;
+
+ public override bool RemoveWhenNotAlive => IsOnPlate;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (CheckPosition == null) return;
@@ -60,32 +62,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
}
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
+
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
- base.SkinChanged(skin, allowFallback);
-
- if (HitObject is IHasComboInformation combo)
- AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
- }
-
- private const float preempt = 1000;
-
- protected override void UpdateState(ArmedState state)
- {
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
- this.FadeIn(200);
-
- var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
+ var endTime = HitObject.GetEndTime();
using (BeginAbsoluteSequence(endTime, true))
{
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire();
+ this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
+
case ArmedState.Hit:
- this.FadeOut().Expire();
+ this.FadeOut();
break;
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index a896d13132..059310d671 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -1,11 +1,10 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -26,20 +25,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = pulp = new Pulp
- {
- Size = Size
- };
- }
+ AddInternal(pulp = new Pulp { Size = Size });
- public override Color4 AccentColour
- {
- get { return base.AccentColour; }
- set
- {
- base.AccentColour = value;
- pulp.AccentColour = AccentColour;
- }
+ AccentColour.BindValueChanged(colour => { pulp.AccentColour = colour.NewValue; }, true);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 4bd50f29f6..53a018c9f4 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -1,13 +1,14 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osuTK;
using osuTK.Graphics;
@@ -40,9 +41,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private void load()
{
// todo: this should come from the skin.
- AccentColour = colourForRepresentation(HitObject.VisualRepresentation);
+ AccentColour.Value = colourForRepresentation(HitObject.VisualRepresentation);
- InternalChildren = new[]
+ AddRangeInternal(new[]
{
createPulp(HitObject.VisualRepresentation),
border = new Circle
@@ -52,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow,
Radius = 4 * radius_adjust,
- Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
+ Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Value.Darken(1).Opacity(0.6f)
},
Size = new Vector2(Height),
Anchor = Anchor.Centre,
@@ -64,13 +65,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
new Box
{
AlwaysPresent = true,
- Colour = AccentColour,
+ Colour = AccentColour.Value,
Alpha = 0,
RelativeSizeAxes = Axes.Both
}
}
},
- };
+ });
if (HitObject.HyperDash)
{
@@ -80,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.Red,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0.5f,
Scale = new Vector2(1.333f)
});
@@ -97,14 +98,15 @@ 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)
{
default:
return new Container();
+
case FruitVisualRepresentation.Raspberry:
return new Container
{
@@ -113,36 +115,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.34f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(0, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(90, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(180, distance_from_centre_4),
},
new Pulp
{
Size = new Vector2(large_pulp_4),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(270, distance_from_centre_4),
},
}
};
+
case FruitVisualRepresentation.Pineapple:
return new Container
{
@@ -151,36 +154,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.3f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(45, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(135, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(225, distance_from_centre_4),
},
new Pulp
{
Size = new Vector2(large_pulp_4),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(315, distance_from_centre_4),
},
}
};
+
case FruitVisualRepresentation.Pear:
return new Container
{
@@ -189,30 +193,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.33f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(60, distance_from_centre_3),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(180, distance_from_centre_3),
},
new Pulp
{
Size = new Vector2(large_pulp_3),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(300, distance_from_centre_3),
},
}
};
+
case FruitVisualRepresentation.Grape:
return new Container
{
@@ -221,30 +226,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.25f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(0, distance_from_centre_3),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(120, distance_from_centre_3),
},
new Pulp
{
Size = new Vector2(large_pulp_3),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(240, distance_from_centre_3),
},
}
};
+
case FruitVisualRepresentation.Banana:
return new Container
{
@@ -253,13 +259,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.3f
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
Y = 0.05f,
},
@@ -272,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)
@@ -282,19 +288,25 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
default:
case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255);
+
case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255);
+
case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255);
+
case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255);
+
case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
+
case 1:
return new Color4(255, 192, 0, 255);
+
case 2:
return new Color4(214, 221, 28, 255);
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
index e66852c5c2..a24821b3ce 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -1,39 +1,51 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableJuiceStream : DrawableCatchHitObject
{
+ private readonly Func> createDrawableRepresentation;
private readonly Container dropletContainer;
- public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null)
+ public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null)
: base(s)
{
+ this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.Both;
Origin = Anchor.BottomLeft;
X = 0;
- InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, };
-
- foreach (var o in s.NestedHitObjects.Cast())
- AddNested(getVisualRepresentation?.Invoke(o));
+ AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- var catchObject = (DrawableCatchHitObject)h;
+ base.AddNestedHitObject(hitObject);
+ dropletContainer.Add(hitObject);
+ }
- catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ dropletContainer.Clear();
+ }
- dropletContainer.Add(h);
- base.AddNested(h);
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case CatchHitObject catchObject:
+ return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
+ }
+
+ return base.CreateNestedHitObject(hitObject);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
index 2f167121aa..d41aea1e7b 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osuTK;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index f21c14f076..1e9daf18db 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -1,9 +1,9 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK.Graphics;
@@ -18,14 +18,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Colour = Color4.White.Opacity(0.9f);
}
private Color4 accentColour;
+
public Color4 AccentColour
{
- get { return accentColour; }
+ get => accentColour;
set
{
accentColour = value;
diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
index 8b54922959..7b0bb3f0ae 100644
--- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
index 2c2cd013c3..6f0423b420 100644
--- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d8bd3e0edc..a4ed966abb 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -1,7 +1,6 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
@@ -25,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Velocity;
public double TickDistance;
+ ///
+ /// The length of one span of this .
+ ///
+ public double SpanDuration => Duration / this.SpanCount();
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -41,93 +45,68 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
- createTicks();
- }
- private void createTicks()
- {
- if (TickDistance == 0)
- return;
-
- var length = Path.Distance;
- var tickDistance = Math.Min(TickDistance, length);
- var spanDuration = length / Velocity;
-
- var minDistanceFromEnd = Velocity * 0.01;
-
- AddNested(new Fruit
+ var tickSamples = Samples.Select(s => new HitSampleInfo
{
- Samples = Samples,
- StartTime = StartTime,
- X = X
- });
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }).ToList();
- double lastDropletTime = StartTime;
+ SliderEventDescriptor? lastEvent = null;
- for (int span = 0; span < this.SpanCount(); span++)
+ foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
- var spanStartTime = StartTime + span * spanDuration;
- var reversed = span % 2 == 1;
-
- for (double d = 0; d <= length; d += tickDistance)
+ // generate tiny droplets since the last point
+ if (lastEvent != null)
{
- var timeProgress = d / length;
- var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
+ double sinceLastTick = e.Time - lastEvent.Value.Time;
- double time = spanStartTime + timeProgress * spanDuration;
-
- if (LegacyLastTickOffset != null)
+ if (sinceLastTick > 80)
{
- // If we're the last tick, apply the legacy offset
- if (span == this.SpanCount() - 1 && d + tickDistance > length)
- time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
- }
+ double timeBetweenTiny = sinceLastTick;
+ while (timeBetweenTiny > 100)
+ timeBetweenTiny /= 2;
- double tinyTickInterval = time - lastDropletTime;
- while (tinyTickInterval > 100)
- tinyTickInterval /= 2;
-
- for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
- {
- double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
-
- AddNested(new TinyDroplet
+ for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
{
- StartTime = t,
- X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
+ AddNested(new TinyDroplet
{
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
+ Samples = tickSamples,
+ StartTime = t + lastEvent.Value.Time,
+ X = X + Path.PositionAt(
+ lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
+ });
+ }
}
-
- if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
- {
- AddNested(new Droplet
- {
- StartTime = time,
- X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
- {
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
- }
-
- lastDropletTime = time;
}
- AddNested(new Fruit
+ // this also includes LegacyLastTick and this is used for TinyDroplet generation above.
+ // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied.
+ lastEvent = e;
+
+ switch (e.Type)
{
- Samples = Samples,
- StartTime = spanStartTime + spanDuration,
- X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
- });
+ case SliderEventType.Tick:
+ AddNested(new Droplet
+ {
+ Samples = tickSamples,
+ StartTime = e.Time,
+ X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
+ });
+ break;
+
+ case SliderEventType.Head:
+ case SliderEventType.Tail:
+ case SliderEventType.Repeat:
+ AddNested(new Fruit
+ {
+ Samples = Samples,
+ StartTime = e.Time,
+ X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
+ });
+ break;
+ }
}
}
@@ -137,17 +116,27 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Duration => EndTime - StartTime;
- private SliderPath path;
+ private readonly SliderPath path = new SliderPath();
public SliderPath Path
{
get => path;
- set => path = value;
+ set
+ {
+ path.ControlPoints.Clear();
+ path.ExpectedDistance.Value = null;
+
+ if (value != null)
+ {
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
+ path.ExpectedDistance.Value = value.ExpectedDistance.Value;
+ }
+ }
}
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/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
index 39f1cadad5..1bf160b5a6 100644
--- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index 045d0824c6..26f20b223a 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System.Runtime.CompilerServices;
@@ -9,3 +9,5 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.iOS")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Android")]
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 20bf2ee5c7..4649dcae90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -1,22 +1,25 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays
{
- internal class CatchAutoGenerator : AutoGenerator
+ internal class CatchAutoGenerator : AutoGenerator
{
public const double RELEASE_DELAY = 20;
- public CatchAutoGenerator(Beatmap beatmap)
+ public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
+
+ public CatchAutoGenerator(IBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay();
@@ -24,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.Replays
protected Replay Replay;
+ private CatchReplayFrame currentFrame;
+
public override Replay Generate()
{
// todo: add support for HT DT
@@ -32,18 +37,18 @@ namespace osu.Game.Rulesets.Catch.Replays
float lastPosition = 0.5f;
double lastTime = 0;
- // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition));
-
void moveToNext(CatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
double timeAvailable = h.StartTime - lastTime;
- //So we can either make it there without a dash or not.
- double speedRequired = positionChange / timeAvailable;
+ // So we can either make it there without a dash or not.
+ // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
+ // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
+ double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable;
- bool dashRequired = speedRequired > movement_speed && h.StartTime != 0;
+ bool dashRequired = speedRequired > movement_speed;
+ bool impossibleJump = speedRequired > movement_speed * 2;
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f;
@@ -52,19 +57,18 @@ namespace osu.Game.Rulesets.Catch.Replays
{
//we are already in the correct range.
lastTime = h.StartTime;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition));
+ addFrame(h.StartTime, lastPosition);
return;
}
- if (h is Banana)
+ if (impossibleJump)
{
- // auto bananas unrealistically warp to catch 100% combo.
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime, h.X);
}
else if (h.HyperDash)
{
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable, lastPosition);
+ addFrame(h.StartTime, h.X);
}
else if (dashRequired)
{
@@ -76,16 +80,16 @@ namespace osu.Game.Rulesets.Catch.Replays
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
+ addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
+ addFrame(h.StartTime, h.X);
}
else
{
double timeBefore = positionChange / movement_speed;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeBefore, lastPosition);
+ addFrame(h.StartTime, h.X);
}
lastTime = h.StartTime;
@@ -117,5 +121,16 @@ namespace osu.Game.Rulesets.Catch.Replays
return Replay;
}
+
+ private void addFrame(double time, float? position = null, bool dashing = false)
+ {
+ // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
+ if (Replay.Frames.Count == 0)
+ Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
+
+ var last = currentFrame;
+ currentFrame = new CatchReplayFrame(time, position, dashing, last);
+ Replay.Frames.Add(currentFrame);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index c907fec653..f122588a2b 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -1,9 +1,11 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using osu.Framework.Input.StateChanges;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
@@ -16,16 +18,20 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
+ protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
protected float? Position
{
get
{
- if (!HasFrames)
+ var frame = CurrentFrame;
+
+ if (frame == null)
return null;
- return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
+ Debug.Assert(CurrentTime != null);
+
+ return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
}
}
@@ -33,21 +39,11 @@ namespace osu.Game.Rulesets.Catch.Replays
{
if (!Position.HasValue) return new List();
- var actions = new List();
-
- if (CurrentFrame.Dashing)
- actions.Add(CatchAction.Dash);
-
- if (Position.Value > CurrentFrame.Position)
- actions.Add(CatchAction.MoveRight);
- else if (Position.Value < CurrentFrame.Position)
- actions.Add(CatchAction.MoveLeft);
-
return new List
{
new CatchReplayState
{
- PressedActions = actions,
+ PressedActions = CurrentFrame?.Actions ?? new List(),
CatcherX = Position.Value
},
};
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index 8c32b75959..b41a5e0612 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -1,6 +1,7 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Catch.UI;
@@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Replays
{
public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
+ public List Actions = new List();
+
public float Position;
public bool Dashing;
@@ -18,17 +21,40 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- public CatchReplayFrame(double time, float? position = null, bool dashing = false)
+ public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null)
: base(time)
{
Position = position ?? -1;
Dashing = dashing;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ if (lastFrame != null)
+ {
+ if (Position > lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveLeft);
+ }
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
- Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
+ Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
+ Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ // this probably needs some cross-checking with osu-stable to ensure it is actually correct.
+ if (lastFrame is CatchReplayFrame lastCatchFrame)
+ {
+ if (Position > lastCatchFrame.Position)
+ lastCatchFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastCatchFrame.Position)
+ Actions.Add(CatchAction.MoveLeft);
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu
new file mode 100644
index 0000000000..ebad654404
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu
@@ -0,0 +1,138 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.3
+Mode: 2
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+34500,-50,4,2,1,50,0,0
+
+[HitObjects]
+// fruits spaced 1/1 beat apart
+32,128,0,5,0,0:0:0:0:
+96,128,500,1,0,0:0:0:0:
+160,128,1000,1,0,0:0:0:0:
+224,128,1500,1,0,0:0:0:0:
+288,128,2000,1,0,0:0:0:0:
+352,128,2500,1,0,0:0:0:0:
+416,128,3000,1,0,0:0:0:0:
+480,128,3500,1,0,0:0:0:0:
+
+// fruits spaced 1/2 beat apart
+32,160,4500,1,0,0:0:0:0:
+64,160,4750,1,0,0:0:0:0:
+96,160,5000,1,0,0:0:0:0:
+128,160,5250,1,0,0:0:0:0:
+160,160,5500,1,0,0:0:0:0:
+192,160,5750,1,0,0:0:0:0:
+224,160,6000,1,0,0:0:0:0:
+256,160,6250,1,0,0:0:0:0:
+288,160,6500,1,0,0:0:0:0:
+
+// fruits spaced 1/4 beat apart
+96,128,7500,1,0,0:0:0:0:
+128,128,7625,1,0,0:0:0:0:
+160,128,7750,1,0,0:0:0:0:
+192,128,7875,1,0,0:0:0:0:
+224,128,8000,1,0,0:0:0:0:
+256,128,8125,1,0,0:0:0:0:
+288,128,8250,1,0,0:0:0:0:
+320,128,8375,1,0,0:0:0:0:
+352,128,8500,1,0,0:0:0:0:
+
+// fruit hyperdashes, spaced 1/2 beat apart
+32,160,9500,1,0,0:0:0:0:
+480,160,9750,1,0,0:0:0:0:
+32,160,10000,1,0,0:0:0:0:
+480,160,10250,1,0,0:0:0:0:
+32,160,10500,1,0,0:0:0:0:
+480,160,10750,1,0,0:0:0:0:
+32,160,11000,1,0,0:0:0:0:
+
+// fruit hyperdashes, spaced 1/4 beat apart
+32,192,12000,1,0,0:0:0:0:
+480,192,12125,1,0,0:0:0:0:
+32,192,12250,1,0,0:0:0:0:
+480,192,12375,1,0,0:0:0:0:
+32,192,12500,1,0,0:0:0:0:
+480,192,12625,1,0,0:0:0:0:
+32,192,12750,1,0,0:0:0:0:
+480,192,12875,1,0,0:0:0:0:
+32,192,13000,1,0,0:0:0:0:
+
+// stream + hyperdash + stream, spaced 1/4 beat apart
+32,192,14000,1,0,0:0:0:0:
+64,192,14125,1,0,0:0:0:0:
+96,192,14250,1,0,0:0:0:0:
+128,192,14375,1,0,0:0:0:0:
+480,192,14500,1,0,0:0:0:0:
+448,192,14625,1,0,0:0:0:0:
+416,192,14750,1,0,0:0:0:0:
+384,192,14875,1,0,0:0:0:0:
+32,192,15000,1,0,0:0:0:0:
+
+// basic sliders
+32,192,16000,2,0,L|192:192,1,160
+224,192,17000,2,0,L|384:192,1,160
+416,192,17875,2,0,L|480:192,1,40
+
+// slider hyperdashes, spaced 1/4 beat apart
+32,192,19000,2,0,L|128:192,1,80
+480,192,19375,2,0,L|384:192,1,80
+352,192,19750,2,0,L|256:192,1,80
+0,192,20125,2,0,L|128:192,1,120
+
+// stream + slider hyperdashes, spaced 1/4 beat apart
+32,192,21500,1,0,0:0:0:0:
+64,192,21625,1,0,0:0:0:0:
+96,192,21750,1,0,0:0:0:0:
+512,192,21875,2,0,L|320:192,1,160
+320,192,22500,1,0,0:0:0:0:
+288,192,22625,1,0,0:0:0:0:
+256,192,22750,1,0,0:0:0:0:
+0,192,22875,2,0,L|64:192,1,40
+
+// streams, spaced 1/4 beat apart
+64,192,24000,1,0,0:0:0:0:
+160,192,24125,1,0,0:0:0:0:
+64,192,24250,1,0,0:0:0:0:
+160,192,24375,1,0,0:0:0:0:
+64,192,24500,1,0,0:0:0:0:
+160,192,24625,1,0,0:0:0:0:
+64,192,24750,1,0,0:0:0:0:
+160,192,24875,1,0,0:0:0:0:
+64,192,25000,1,0,0:0:0:0:
+160,192,25125,1,0,0:0:0:0:
+64,192,25250,1,0,0:0:0:0:
+160,192,25375,1,0,0:0:0:0:
+64,192,25500,1,0,0:0:0:0:
+
+// stream + spinner combo, spaced 1/4 beat apart
+256,192,26500,12,0,27000,0:0:0:0:
+128,192,27250,5,0,0:0:0:0:
+128,192,27375,1,0,0:0:0:0:
+160,192,27500,1,0,0:0:0:0:
+192,192,27625,1,0,0:0:0:0:
+256,192,27750,12,0,28500,0:0:0:0:
+192,192,28625,5,0,0:0:0:0:
+224,192,28750,1,0,0:0:0:0:
+256,192,28875,1,0,0:0:0:0:
+256,192,29000,1,0,0:0:0:0:
+256,192,29125,12,0,29500,0:0:0:0:
+
+// long slow slider
+0,192,30500,6,0,B|480:192|480:192|0:192,2,960
+
+// long fast slider
+0,192,37500,6,0,B|480:192|480:192|0:192,2,960
+
+// long hyperdash slider
+0,192,41500,2,0,P|544:192|544:192,5,480
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..83f9e30800
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
@@ -0,0 +1,150 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 177
+ },
+ {
+ "StartTime": 450,
+ "Position": 216.539276
+ },
+ {
+ "StartTime": 532,
+ "Position": 256.5667
+ },
+ {
+ "StartTime": 614,
+ "Position": 296.594116
+ },
+ {
+ "StartTime": 696,
+ "Position": 336.621521
+ },
+ {
+ "StartTime": 778,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 860,
+ "Position": 337.318878
+ },
+ {
+ "StartTime": 942,
+ "Position": 297.291443
+ },
+ {
+ "StartTime": 1024,
+ "Position": 257.264038
+ },
+ {
+ "StartTime": 1106,
+ "Position": 217.2366
+ },
+ {
+ "StartTime": 1188,
+ "Position": 177
+ },
+ {
+ "StartTime": 1270,
+ "Position": 216.818192
+ },
+ {
+ "StartTime": 1352,
+ "Position": 256.8456
+ },
+ {
+ "StartTime": 1434,
+ "Position": 296.873047
+ },
+ {
+ "StartTime": 1516,
+ "Position": 336.900452
+ },
+ {
+ "StartTime": 1598,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 1680,
+ "Position": 337.039948
+ },
+ {
+ "StartTime": 1762,
+ "Position": 297.0125
+ },
+ {
+ "StartTime": 1844,
+ "Position": 256.9851
+ },
+ {
+ "StartTime": 1926,
+ "Position": 216.957672
+ },
+ {
+ "StartTime": 2008,
+ "Position": 177
+ },
+ {
+ "StartTime": 2090,
+ "Position": 217.097137
+ },
+ {
+ "StartTime": 2172,
+ "Position": 257.124573
+ },
+ {
+ "StartTime": 2254,
+ "Position": 297.152
+ },
+ {
+ "StartTime": 2336,
+ "Position": 337.179443
+ },
+ {
+ "StartTime": 2418,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 2500,
+ "Position": 336.760956
+ },
+ {
+ "StartTime": 2582,
+ "Position": 296.733643
+ },
+ {
+ "StartTime": 2664,
+ "Position": 256.7062
+ },
+ {
+ "StartTime": 2746,
+ "Position": 216.678772
+ },
+ {
+ "StartTime": 2828,
+ "Position": 177
+ },
+ {
+ "StartTime": 2909,
+ "Position": 216.887909
+ },
+ {
+ "StartTime": 2991,
+ "Position": 256.915344
+ },
+ {
+ "StartTime": 3073,
+ "Position": 296.942749
+ },
+ {
+ "StartTime": 3155,
+ "Position": 336.970184
+ },
+ {
+ "StartTime": 3237,
+ "Position": 376.99762
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
new file mode 100644
index 0000000000..c1971edf2c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+177,191,369,6,0,L|382:192,7,200
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
new file mode 100644
index 0000000000..7333b600fb
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
@@ -0,0 +1,74 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 65
+ },
+ {
+ "StartTime": 450,
+ "Position": 482
+ },
+ {
+ "StartTime": 532,
+ "Position": 164
+ },
+ {
+ "StartTime": 614,
+ "Position": 315
+ },
+ {
+ "StartTime": 696,
+ "Position": 145
+ },
+ {
+ "StartTime": 778,
+ "Position": 159
+ },
+ {
+ "StartTime": 860,
+ "Position": 310
+ },
+ {
+ "StartTime": 942,
+ "Position": 441
+ },
+ {
+ "StartTime": 1024,
+ "Position": 428
+ },
+ {
+ "StartTime": 1106,
+ "Position": 243
+ },
+ {
+ "StartTime": 1188,
+ "Position": 422
+ },
+ {
+ "StartTime": 1270,
+ "Position": 481
+ },
+ {
+ "StartTime": 1352,
+ "Position": 104
+ },
+ {
+ "StartTime": 1434,
+ "Position": 473
+ },
+ {
+ "StartTime": 1516,
+ "Position": 135
+ },
+ {
+ "StartTime": 1598,
+ "Position": 360
+ },
+ {
+ "StartTime": 1680,
+ "Position": 123
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
new file mode 100644
index 0000000000..208d21a344
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+256,192,369,12,0,1680,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
new file mode 100644
index 0000000000..bbc16ab912
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
@@ -0,0 +1,234 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 450,
+ "Objects": [{
+ "StartTime": 450,
+ "Position": 254
+ }]
+ },
+ {
+ "StartTime": 532,
+ "Objects": [{
+ "StartTime": 532,
+ "Position": 241
+ }]
+ },
+ {
+ "StartTime": 614,
+ "Objects": [{
+ "StartTime": 614,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 696,
+ "Objects": [{
+ "StartTime": 696,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 778,
+ "Objects": [{
+ "StartTime": 778,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 860,
+ "Objects": [{
+ "StartTime": 860,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 942,
+ "Objects": [{
+ "StartTime": 942,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1024,
+ "Objects": [{
+ "StartTime": 1024,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1106,
+ "Objects": [{
+ "StartTime": 1106,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1188,
+ "Objects": [{
+ "StartTime": 1188,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1270,
+ "Objects": [{
+ "StartTime": 1270,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1352,
+ "Objects": [{
+ "StartTime": 1352,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1434,
+ "Objects": [{
+ "StartTime": 1434,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 1516,
+ "Objects": [{
+ "StartTime": 1516,
+ "Position": 253
+ }]
+ },
+ {
+ "StartTime": 1598,
+ "Objects": [{
+ "StartTime": 1598,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1680,
+ "Objects": [{
+ "StartTime": 1680,
+ "Position": 260
+ }]
+ },
+ {
+ "StartTime": 1762,
+ "Objects": [{
+ "StartTime": 1762,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1844,
+ "Objects": [{
+ "StartTime": 1844,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1926,
+ "Objects": [{
+ "StartTime": 1926,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2008,
+ "Objects": [{
+ "StartTime": 2008,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2090,
+ "Objects": [{
+ "StartTime": 2090,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2172,
+ "Objects": [{
+ "StartTime": 2172,
+ "Position": 243
+ }]
+ },
+ {
+ "StartTime": 2254,
+ "Objects": [{
+ "StartTime": 2254,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2336,
+ "Objects": [{
+ "StartTime": 2336,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2418,
+ "Objects": [{
+ "StartTime": 2418,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2500,
+ "Objects": [{
+ "StartTime": 2500,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 2582,
+ "Objects": [{
+ "StartTime": 2582,
+ "Position": 256
+ }]
+ },
+ {
+ "StartTime": 2664,
+ "Objects": [{
+ "StartTime": 2664,
+ "Position": 242
+ }]
+ },
+ {
+ "StartTime": 2746,
+ "Objects": [{
+ "StartTime": 2746,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2828,
+ "Objects": [{
+ "StartTime": 2828,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2909,
+ "Objects": [{
+ "StartTime": 2909,
+ "Position": 271
+ }]
+ },
+ {
+ "StartTime": 2991,
+ "Objects": [{
+ "StartTime": 2991,
+ "Position": 254
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
new file mode 100644
index 0000000000..321af4604b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
@@ -0,0 +1,50 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+258,189,369,1,0,0:0:0:0:
+258,189,450,1,0,0:0:0:0:
+258,189,532,1,0,0:0:0:0:
+258,189,614,1,0,0:0:0:0:
+258,189,696,1,0,0:0:0:0:
+258,189,778,1,0,0:0:0:0:
+258,189,860,1,0,0:0:0:0:
+258,189,942,1,0,0:0:0:0:
+258,189,1024,1,0,0:0:0:0:
+258,189,1106,1,0,0:0:0:0:
+258,189,1188,1,0,0:0:0:0:
+258,189,1270,1,0,0:0:0:0:
+258,189,1352,1,0,0:0:0:0:
+258,189,1434,1,0,0:0:0:0:
+258,189,1516,1,0,0:0:0:0:
+258,189,1598,1,0,0:0:0:0:
+258,189,1680,1,0,0:0:0:0:
+258,189,1762,1,0,0:0:0:0:
+258,189,1844,1,0,0:0:0:0:
+258,189,1926,1,0,0:0:0:0:
+258,189,2008,1,0,0:0:0:0:
+258,189,2090,1,0,0:0:0:0:
+258,189,2172,1,0,0:0:0:0:
+258,189,2254,1,0,0:0:0:0:
+258,189,2336,1,0,0:0:0:0:
+258,189,2418,1,0,0:0:0:0:
+258,189,2500,1,0,0:0:0:0:
+258,189,2582,1,0,0:0:0:0:
+258,189,2664,1,0,0:0:0:0:
+258,189,2746,1,0,0:0:0:0:
+258,189,2828,1,0,0:0:0:0:
+258,189,2909,1,0,0:0:0:0:
+258,189,2991,1,0,0:0:0:0:
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
new file mode 100644
index 0000000000..58c52b6867
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json
@@ -0,0 +1 @@
+{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu
new file mode 100644
index 0000000000..d48b2d7769
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+Mode: 2
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:2
+OverallDifficulty:4
+ApproachRate:4
+SliderMultiplier:0.9
+SliderTickRate:1
+
+[TimingPoints]
+35.4473684210527,638.298947368422,4,2,1,60,1,0
+
+[HitObjects]
+320,176,19184,2,8,P|384:168|368:232,1,150
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
new file mode 100644
index 0000000000..ff793a372e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Scoring
+{
+ public class CatchHitWindows : HitWindows
+ {
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Perfect:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 778d972b52..4c7bc4ab73 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,45 +1,12 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
-using System;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
- public class CatchScoreProcessor : ScoreProcessor
+ public class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(RulesetContainer rulesetContainer)
- : base(rulesetContainer)
- {
- }
-
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- private const double harshness = 0.01;
-
- protected override void ApplyResult(JudgementResult result)
- {
- base.ApplyResult(result);
-
- if (result.Type == HitResult.Miss)
- {
- if (!result.Judgement.IsBonus)
- Health.Value -= hpDrainRate * (harshness * 2);
- return;
- }
-
- Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
- }
+ public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 0697a72325..589503c35b 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -1,5 +1,5 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
@@ -20,33 +20,26 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea;
- public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation)
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos);
+
+ public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation)
{
Container explodingFruitContainer;
- Anchor = Anchor.TopCentre;
- Origin = Anchor.TopCentre;
-
- Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
-
- InternalChild = new PlayfieldAdjustmentContainer
+ InternalChildren = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ explodingFruitContainer = new Container
{
- explodingFruitContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- CatcherArea = new CatcherArea(difficulty)
- {
- GetVisualRepresentation = getVisualRepresentation,
- ExplodingFruitTarget = explodingFruitContainer,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.TopLeft,
- },
- HitObjectContainer
- }
+ RelativeSizeAxes = Axes.Both,
+ },
+ CatcherArea = new CatcherArea(difficulty)
+ {
+ CreateDrawableRepresentation = createDrawableRepresentation,
+ ExplodingFruitTarget = explodingFruitContainer,
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.TopLeft,
+ },
+ HitObjectContainer
};
}
diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
similarity index 69%
rename from osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs
rename to osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
index fa1a7ee868..b8d3dc9017 100644
--- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
@@ -1,19 +1,25 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
- public class PlayfieldAdjustmentContainer : Container
+ public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
{
protected override Container Content => content;
private readonly Container content;
- public PlayfieldAdjustmentContainer()
+ public CatchPlayfieldAdjustmentContainer()
{
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+
+ Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
+
InternalChild = new Container
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 3637fe0c36..1de0b6bfa3 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -1,15 +1,13 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+// Copyright (c) ppy Pty Ltd