mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 05:39:53 +08:00
Compare commits
228 Commits
2021.405.0
...
2021.410.0
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ContentModelUserStore">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/riderModule.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -41,7 +41,7 @@ If your platform is not listed above, there is still a chance you can manually b
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
||||
osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu-templates).
|
||||
osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
||||
|
||||
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852).
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# osu-templates
|
||||
# Templates
|
||||
|
||||
Templates for use when creating osu! dependent projects. Create a fully-testable (and ready for git) custom ruleset in just two lines.
|
||||
|
||||
|
||||
@@ -12,16 +12,189 @@ 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
|
||||
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 = false: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
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
+477
-420
File diff suppressed because it is too large
Load Diff
@@ -12,16 +12,189 @@ 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
|
||||
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 = false: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
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,16 +12,189 @@ 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
|
||||
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 = false: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
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
+477
-420
File diff suppressed because it is too large
Load Diff
@@ -12,16 +12,189 @@ 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
|
||||
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 = false: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
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
+473
-416
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.402.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.410.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.410.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
<AndroidDexTool>d8</AndroidDexTool>
|
||||
<AndroidLinkTool>r8</AndroidLinkTool>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GameplayScreenRotationLocker.cs" />
|
||||
<Compile Include="OsuGameActivity.cs" />
|
||||
|
||||
+11
-2
@@ -15,7 +15,16 @@
|
||||
"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"
|
||||
"osu.Game\\osu.Game.csproj",
|
||||
|
||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainActivity.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public class TestSceneCatchReplay : TestSceneCatchPlayer
|
||||
{
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
private const int object_count = 10;
|
||||
|
||||
[Test]
|
||||
public void TestReplayCatcherPositionIsFramePerfect()
|
||||
{
|
||||
AddUntilStep("caught all fruits", () => Player.ScoreProcessor.Combo.Value == object_count);
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
}
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
for (int i = 0; i < object_count / 2; i++)
|
||||
{
|
||||
beatmap.HitObjects.Add(new Fruit
|
||||
{
|
||||
StartTime = (i + 1) * 1000,
|
||||
X = 0
|
||||
});
|
||||
beatmap.HitObjects.Add(new Fruit
|
||||
{
|
||||
StartTime = (i + 1) * 1000 + 1,
|
||||
X = CatchPlayfield.WIDTH
|
||||
});
|
||||
}
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
private const double star_scaling_factor = 0.153;
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
private float halfCatcherWidth;
|
||||
|
||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
|
||||
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
{
|
||||
public class Movement : Skill
|
||||
public class Movement : StrainSkill
|
||||
{
|
||||
private const float absolute_player_positioning_error = 16f;
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
@@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
|
||||
protected override double DecayWeight => 0.94;
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
protected readonly float HalfCatcherWidth;
|
||||
|
||||
private float? lastPlayerPosition;
|
||||
|
||||
@@ -51,8 +51,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
droppedObjectContainer,
|
||||
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||
HitObjectContainer,
|
||||
HitObjectContainer.CreateProxy(),
|
||||
// This ordering (`CatcherArea` before `HitObjectContainer`) is important to
|
||||
// make sure the up-to-date catcher position is used for the catcher catching logic of hit objects.
|
||||
CatcherArea,
|
||||
HitObjectContainer,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainActivity.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,11 +7,10 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
{
|
||||
public class Strain : Skill
|
||||
public class Strain : StrainSkill
|
||||
{
|
||||
private const double individual_decay_base = 0.125;
|
||||
private const double overall_decay_base = 0.30;
|
||||
@@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||
var endTime = maniaCurrent.BaseObject.GetEndTime();
|
||||
var endTime = maniaCurrent.EndTime;
|
||||
var column = maniaCurrent.BaseObject.Column;
|
||||
|
||||
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
||||
@@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
for (int i = 0; i < holdEndTimes.Length; ++i)
|
||||
{
|
||||
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
||||
if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1))
|
||||
if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1))
|
||||
holdAddition = 1.0;
|
||||
|
||||
// ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
internal class ManiaScoreProcessor : ScoreProcessor
|
||||
{
|
||||
protected override double DefaultAccuracyPortion => 0.95;
|
||||
protected override double DefaultAccuracyPortion => 0.99;
|
||||
|
||||
protected override double DefaultComboPortion => 0.05;
|
||||
protected override double DefaultComboPortion => 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainActivity.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -14,7 +16,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public class TestScenePathControlPointVisualiser : OsuTestScene
|
||||
public class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Slider slider;
|
||||
private PathControlPointVisualiser visualiser;
|
||||
@@ -43,12 +45,145 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveTooManyPoints()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.Bezier);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
// Must be both hovering and selecting the control point for the context menu to work.
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
|
||||
addContextMenuItemStep("Perfect curve");
|
||||
|
||||
assertControlPointPathType(0, PathType.Bezier);
|
||||
assertControlPointPathType(1, PathType.PerfectCurve);
|
||||
assertControlPointPathType(3, PathType.Bezier);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveLastThreePoints()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.Bezier);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
moveMouseToControlPoint(2);
|
||||
AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true);
|
||||
addContextMenuItemStep("Perfect curve");
|
||||
|
||||
assertControlPointPathType(0, PathType.Bezier);
|
||||
assertControlPointPathType(2, PathType.PerfectCurve);
|
||||
assertControlPointPathType(4, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveLastTwoPoints()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.Bezier);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
moveMouseToControlPoint(3);
|
||||
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
|
||||
addContextMenuItemStep("Perfect curve");
|
||||
|
||||
assertControlPointPathType(0, PathType.Bezier);
|
||||
AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveTooManyPointsLinear()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.Linear);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
// Must be both hovering and selecting the control point for the context menu to work.
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
|
||||
addContextMenuItemStep("Perfect curve");
|
||||
|
||||
assertControlPointPathType(0, PathType.Linear);
|
||||
assertControlPointPathType(1, PathType.PerfectCurve);
|
||||
assertControlPointPathType(3, PathType.Linear);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveChangeToBezier()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.Bezier);
|
||||
addControlPointStep(new Vector2(300), PathType.PerfectCurve);
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200), PathType.Bezier);
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
moveMouseToControlPoint(3);
|
||||
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
|
||||
addContextMenuItemStep("Inherit");
|
||||
|
||||
assertControlPointPathType(0, PathType.Bezier);
|
||||
assertControlPointPathType(1, PathType.Bezier);
|
||||
assertControlPointPathType(3, null);
|
||||
}
|
||||
|
||||
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
|
||||
private void addControlPointStep(Vector2 position) => AddStep($"add control point {position}", () => slider.Path.ControlPoints.Add(new PathControlPoint(position)));
|
||||
private void addControlPointStep(Vector2 position) => addControlPointStep(position, null);
|
||||
|
||||
private void addControlPointStep(Vector2 position, PathType? type)
|
||||
{
|
||||
AddStep($"add {type} control point at {position}", () =>
|
||||
{
|
||||
slider.Path.ControlPoints.Add(new PathControlPoint(position, type));
|
||||
});
|
||||
}
|
||||
|
||||
private void moveMouseToControlPoint(int index)
|
||||
{
|
||||
AddStep($"move mouse to control point {index}", () =>
|
||||
{
|
||||
Vector2 position = slider.Path.ControlPoints[index].Position.Value;
|
||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertControlPointPathType(int controlPointIndex, PathType? type)
|
||||
{
|
||||
AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type.Value == type);
|
||||
}
|
||||
|
||||
private void addContextMenuItemStep(string contextMenuText)
|
||||
{
|
||||
AddStep($"click context menu item \"{contextMenuText}\"", () =>
|
||||
{
|
||||
MenuItem item = visualiser.ContextMenuItems[1].Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
||||
|
||||
item?.Action?.Value();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public class TestSceneSliderControlPointPiece : SelectionBlueprintTestScene
|
||||
{
|
||||
private Slider slider;
|
||||
private DrawableSlider drawableObject;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Clear();
|
||||
|
||||
slider = new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||
new PathControlPoint(new Vector2(150, 150)),
|
||||
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve),
|
||||
new PathControlPoint(new Vector2(400, 0)),
|
||||
new PathControlPoint(new Vector2(400, 150))
|
||||
})
|
||||
};
|
||||
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||
|
||||
Add(drawableObject = new DrawableSlider(slider));
|
||||
AddBlueprint(new TestSliderBlueprint(drawableObject));
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestDragControlPoint()
|
||||
{
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
addMovementStep(new Vector2(150, 50));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertControlPointPosition(1, new Vector2(150, 50));
|
||||
assertControlPointType(0, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragControlPointAlmostLinearlyExterior()
|
||||
{
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
addMovementStep(new Vector2(400, 0.01f));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertControlPointPosition(1, new Vector2(400, 0.01f));
|
||||
assertControlPointType(0, PathType.Bezier);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragControlPointPathRecovery()
|
||||
{
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
addMovementStep(new Vector2(400, 0.01f));
|
||||
assertControlPointType(0, PathType.Bezier);
|
||||
|
||||
addMovementStep(new Vector2(150, 50));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertControlPointPosition(1, new Vector2(150, 50));
|
||||
assertControlPointType(0, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragControlPointPathRecoveryOtherSegment()
|
||||
{
|
||||
moveMouseToControlPoint(4);
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
addMovementStep(new Vector2(350, 0.01f));
|
||||
assertControlPointType(2, PathType.Bezier);
|
||||
|
||||
addMovementStep(new Vector2(150, 150));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertControlPointPosition(4, new Vector2(150, 150));
|
||||
assertControlPointType(2, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragControlPointPathAfterChangingType()
|
||||
{
|
||||
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type.Value = PathType.Bezier);
|
||||
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
|
||||
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type.Value = PathType.PerfectCurve);
|
||||
|
||||
moveMouseToControlPoint(4);
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
assertControlPointType(3, PathType.PerfectCurve);
|
||||
|
||||
addMovementStep(new Vector2(350, 0.01f));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertControlPointPosition(4, new Vector2(350, 0.01f));
|
||||
assertControlPointType(3, PathType.Bezier);
|
||||
}
|
||||
|
||||
private void addMovementStep(Vector2 relativePosition)
|
||||
{
|
||||
AddStep($"move mouse to {relativePosition}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + relativePosition;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
private void moveMouseToControlPoint(int index)
|
||||
{
|
||||
AddStep($"move mouse to control point {index}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type.Value == type);
|
||||
|
||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position.Value, 1));
|
||||
|
||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||
{
|
||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
|
||||
public TestSliderBlueprint(DrawableSlider slider)
|
||||
: base(slider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
||||
}
|
||||
|
||||
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
||||
{
|
||||
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||
|
||||
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
||||
: base(slider, position)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,104 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointType(0, PathType.Linear);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior()
|
||||
{
|
||||
Vector2 startPosition = new Vector2(200);
|
||||
|
||||
addMovementStep(startPosition);
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(300, 0));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(150, 0.1f));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.Bezier);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacePerfectCurveSegmentRecovery()
|
||||
{
|
||||
Vector2 startPosition = new Vector2(200);
|
||||
|
||||
addMovementStep(startPosition);
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(300, 0));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(150, 0.1f)); // Should convert to bezier
|
||||
addMovementStep(startPosition + new Vector2(400.0f, 50.0f)); // Should convert back to perfect
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacePerfectCurveSegmentLarge()
|
||||
{
|
||||
Vector2 startPosition = new Vector2(400);
|
||||
|
||||
addMovementStep(startPosition);
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(220, 220));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
// Playfield dimensions are 640 x 480.
|
||||
// So a 440 x 440 bounding box should be ok.
|
||||
addMovementStep(startPosition + new Vector2(-220, 220));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacePerfectCurveSegmentTooLarge()
|
||||
{
|
||||
Vector2 startPosition = new Vector2(480, 200);
|
||||
|
||||
addMovementStep(startPosition);
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(startPosition + new Vector2(400, 400));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
// Playfield dimensions are 640 x 480.
|
||||
// So an 800 * 800 bounding box area should not be ok.
|
||||
addMovementStep(startPosition + new Vector2(-400, 400));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.Bezier);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacePerfectCurveSegmentCompleteArc()
|
||||
{
|
||||
addMovementStep(new Vector2(400));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(600, 400));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(400, 410));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PerfectCurve);
|
||||
}
|
||||
|
||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||
|
||||
private void addClickStep(MouseButton button)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase("repeat-slider")]
|
||||
[TestCase("uneven-repeat-slider")]
|
||||
[TestCase("old-stacking")]
|
||||
[TestCase("multi-segment-slider")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
get
|
||||
{
|
||||
if (content == null)
|
||||
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
|
||||
base.Content.Add(content = new OsuInputManager(new OsuRuleset().RulesetInfo));
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// <summary>
|
||||
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
|
||||
/// </summary>
|
||||
public class Aim : Skill
|
||||
public class Aim : StrainSkill
|
||||
{
|
||||
private const double angle_bonus_begin = Math.PI / 3;
|
||||
private const double timing_threshold = 107;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// <summary>
|
||||
/// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
|
||||
/// </summary>
|
||||
public class Speed : Skill
|
||||
public class Speed : StrainSkill
|
||||
{
|
||||
private const double single_spacing_threshold = 125;
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -28,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
|
||||
{
|
||||
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
||||
public List<PathControlPoint> PointsInSegment;
|
||||
|
||||
public readonly BindableBool IsSelected = new BindableBool();
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
@@ -54,6 +59,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
this.slider = slider;
|
||||
ControlPoint = controlPoint;
|
||||
|
||||
slider.Path.Version.BindValueChanged(_ =>
|
||||
{
|
||||
PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
||||
updatePathType();
|
||||
}, runOnceImmediately: true);
|
||||
|
||||
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
@@ -150,6 +161,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||
|
||||
private Vector2 dragStartPosition;
|
||||
private PathType? dragPathType;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
@@ -159,6 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
dragStartPosition = ControlPoint.Position.Value;
|
||||
dragPathType = PointsInSegment[0].Type.Value;
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
return true;
|
||||
}
|
||||
@@ -184,10 +198,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
else
|
||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||
|
||||
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||
PointsInSegment[0].Type.Value = dragPathType;
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
/// </summary>
|
||||
private void updatePathType()
|
||||
{
|
||||
if (ControlPoint.Type.Value != PathType.PerfectCurve)
|
||||
return;
|
||||
|
||||
if (PointsInSegment.Count > 3)
|
||||
ControlPoint.Type.Value = PathType.Bezier;
|
||||
|
||||
if (PointsInSegment.Count != 3)
|
||||
return;
|
||||
|
||||
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position.Value).ToArray();
|
||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||
ControlPoint.Type.Value = PathType.Bezier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the circular control point marker.
|
||||
/// </summary>
|
||||
|
||||
+29
-1
@@ -153,6 +153,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the given control point piece to the given path type.
|
||||
/// If that would fail, try to change the path such that it instead succeeds
|
||||
/// in a UX-friendly way.
|
||||
/// </summary>
|
||||
/// <param name="piece">The control point piece that we want to change the path type of.</param>
|
||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||
private void updatePathType(PathControlPointPiece piece, PathType? type)
|
||||
{
|
||||
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case PathType.PerfectCurve:
|
||||
// Can't always create a circular arc out of 4 or more points,
|
||||
// so we split the segment into one 3-point circular arc segment
|
||||
// and one segment of the previous type.
|
||||
int thirdPointIndex = indexInSegment + 2;
|
||||
|
||||
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
|
||||
piece.PointsInSegment[thirdPointIndex].Type.Value = piece.PointsInSegment[0].Type.Value;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
piece.ControlPoint.Type.Value = type;
|
||||
}
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
@@ -218,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
var item = new PathTypeMenuItem(type, () =>
|
||||
{
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
p.ControlPoint.Type.Value = type;
|
||||
updatePathType(p, type);
|
||||
});
|
||||
|
||||
if (countOfState == totalCount)
|
||||
|
||||
@@ -142,6 +142,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
base.Update();
|
||||
updateSlider();
|
||||
|
||||
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||
updatePathType();
|
||||
}
|
||||
|
||||
private void updatePathType()
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.OnOperationEnded();
|
||||
referenceOrigin = null;
|
||||
referencePathTypes = null;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||
@@ -53,6 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// </summary>
|
||||
private Vector2? referenceOrigin;
|
||||
|
||||
/// <summary>
|
||||
/// During a transform, the initial path types of a single selected slider are stored so they
|
||||
/// can be maintained throughout the operation.
|
||||
/// </summary>
|
||||
private List<PathType?> referencePathTypes;
|
||||
|
||||
public override bool HandleReverse()
|
||||
{
|
||||
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
||||
@@ -194,6 +201,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private void scaleSlider(Slider slider, Vector2 scale)
|
||||
{
|
||||
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
||||
|
||||
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
||||
|
||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||
@@ -209,6 +218,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
point.Position.Value *= pathRelativeDeltaScale;
|
||||
}
|
||||
|
||||
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
||||
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
||||
slider.Path.ControlPoints[i].Type.Value = referencePathTypes[i];
|
||||
|
||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
@@ -108,16 +109,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
base.LoadSamples();
|
||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||
|
||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||
|
||||
if (firstSample != null)
|
||||
if (HitObject.SampleControlPoint == null)
|
||||
{
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
||||
|
||||
slidingSample.Samples = new ISampleInfo[] { clone };
|
||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||
}
|
||||
|
||||
Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||
|
||||
var slidingSamples = new List<ISampleInfo>();
|
||||
|
||||
var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
|
||||
if (normalSample != null)
|
||||
slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide"));
|
||||
|
||||
var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||
if (whistleSample != null)
|
||||
slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle"));
|
||||
|
||||
slidingSample.Samples = slidingSamples.ToArray();
|
||||
}
|
||||
|
||||
public override void StopAllSamples()
|
||||
|
||||
@@ -81,6 +81,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||
|
||||
[JsonIgnore]
|
||||
public IList<HitSampleInfo> TailSamples { get; private set; }
|
||||
|
||||
private int repeatCount;
|
||||
|
||||
public int RepeatCount
|
||||
@@ -143,11 +146,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
|
||||
// The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to.
|
||||
// For now, the samples are attached to and played by the slider itself at the correct end time.
|
||||
// ToArray call is required as GetNodeSamples may fallback to Samples itself (without it it will get cleared due to the list reference being live).
|
||||
Samples = this.GetNodeSamples(repeatCount + 1).ToArray();
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
@@ -238,6 +236,10 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
if (HeadCircle != null)
|
||||
HeadCircle.Samples = this.GetNodeSamples(0);
|
||||
|
||||
// The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to.
|
||||
// For now, the samples are played by the slider itself at the correct end time.
|
||||
TailSamples = this.GetNodeSamples(repeatCount + 1);
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement();
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 347893,
|
||||
"Objects": [{
|
||||
"StartTime": 347893,
|
||||
"EndTime": 347893,
|
||||
"X": 329,
|
||||
"Y": 245,
|
||||
"StackOffset": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"StartTime": 348193,
|
||||
"EndTime": 348193,
|
||||
"X": 183.0447,
|
||||
"Y": 245.24292,
|
||||
"StackOffset": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"StartTime": 348457,
|
||||
"EndTime": 348457,
|
||||
"X": 329,
|
||||
"Y": 245,
|
||||
"StackOffset": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8
|
||||
SliderMultiplier:2
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
337093,300,4,2,1,40,1,0
|
||||
|
||||
[HitObjects]
|
||||
329,245,347893,2,0,B|319:311|199:343|183:245|183:245,2,200,8|8|8,0:0|0:0|0:0,0:0:0:0:
|
||||
@@ -14,6 +14,11 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainActivity.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new TaikoInputManager(new RulesetInfo { ID = 1 })
|
||||
SetContents(() => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
/// <summary>
|
||||
/// Calculates the colour coefficient of taiko difficulty.
|
||||
/// </summary>
|
||||
public class Colour : Skill
|
||||
public class Colour : StrainSkill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
/// <summary>
|
||||
/// Calculates the rhythm coefficient of taiko difficulty.
|
||||
/// </summary>
|
||||
public class Rhythm : Skill
|
||||
public class Rhythm : StrainSkill
|
||||
{
|
||||
protected override double SkillMultiplier => 10;
|
||||
protected override double StrainDecayBase => 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
/// <remarks>
|
||||
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
||||
/// </remarks>
|
||||
public class Stamina : Skill
|
||||
public class Stamina : StrainSkill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
@@ -133,11 +133,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
List<double> peaks = new List<double>();
|
||||
|
||||
for (int i = 0; i < colour.StrainPeaks.Count; i++)
|
||||
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
|
||||
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
|
||||
var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList();
|
||||
var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList();
|
||||
|
||||
for (int i = 0; i < colourPeaks.Count; i++)
|
||||
{
|
||||
double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
|
||||
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
|
||||
peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
<MandroidI18n>cjk;mideast;other;rare;west</MandroidI18n>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
|
||||
@@ -707,6 +707,69 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0)));
|
||||
Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null));
|
||||
|
||||
// Last control point duplicated
|
||||
var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
|
||||
|
||||
Assert.That(fourth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(fourth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
|
||||
Assert.That(fourth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
|
||||
Assert.That(fourth.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fourth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
|
||||
Assert.That(fourth.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fourth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
||||
Assert.That(fourth.ControlPoints[3].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fourth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
||||
Assert.That(fourth.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
||||
|
||||
// Last control point in segment duplicated
|
||||
var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
|
||||
|
||||
Assert.That(fifth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(fifth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
|
||||
Assert.That(fifth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
|
||||
Assert.That(fifth.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fifth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
|
||||
Assert.That(fifth.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fifth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
||||
Assert.That(fifth.ControlPoints[3].Type.Value, Is.EqualTo(null));
|
||||
Assert.That(fifth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
||||
Assert.That(fifth.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
||||
|
||||
Assert.That(fifth.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(4, 4)));
|
||||
Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier));
|
||||
Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5)));
|
||||
Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null));
|
||||
|
||||
// Implicit perfect-curve multi-segment(Should convert to bezier to match stable)
|
||||
var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
|
||||
|
||||
Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier);
|
||||
Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
|
||||
Assert.That(sixth.ControlPoints[1].Type.Value == null);
|
||||
Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
|
||||
|
||||
Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier);
|
||||
Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
|
||||
Assert.That(sixth.ControlPoints[3].Type.Value == null);
|
||||
Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
|
||||
Assert.That(sixth.ControlPoints[4].Type.Value == null);
|
||||
|
||||
// Explicit perfect-curve multi-segment(Should not convert to bezier)
|
||||
var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
|
||||
|
||||
Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve);
|
||||
Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
|
||||
Assert.That(seventh.ControlPoints[1].Type.Value == null);
|
||||
Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
|
||||
|
||||
Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve);
|
||||
Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
|
||||
Assert.That(seventh.ControlPoints[3].Type.Value == null);
|
||||
Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
|
||||
Assert.That(seventh.ControlPoints[4].Type.Value == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ using osu.Game.IO;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
@@ -45,6 +49,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(0.6f),
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero, PathType.Bezier),
|
||||
new PathControlPoint(new Vector2(0.5f)),
|
||||
new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int).
|
||||
new PathControlPoint(new Vector2(1f), PathType.Bezier),
|
||||
new PathControlPoint(new Vector2(2f))
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty);
|
||||
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
|
||||
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
||||
{
|
||||
// equal to null, no need to SequenceEqual
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class LimitedCapacityStackTest
|
||||
{
|
||||
private const int capacity = 3;
|
||||
|
||||
private LimitedCapacityStack<int> stack;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
stack = new LimitedCapacityStack<int>(capacity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyStack()
|
||||
{
|
||||
Assert.AreEqual(0, stack.Count);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[0];
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
foreach (var unused in stack)
|
||||
count++;
|
||||
|
||||
Assert.AreEqual(0, count);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void TestInRangeElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(count, stack.Count);
|
||||
|
||||
// e.g. 2 -> 1 -> 0 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestOverflowElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(capacity, stack.Count);
|
||||
|
||||
// e.g. 3 -> 2 -> 1 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestEnumerator(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
int enumeratorCount = 0;
|
||||
int expectedValue = count - 1;
|
||||
|
||||
foreach (var item in stack)
|
||||
{
|
||||
Assert.AreEqual(expectedValue, item);
|
||||
enumeratorCount++;
|
||||
expectedValue--;
|
||||
}
|
||||
|
||||
Assert.AreEqual(stack.Count, enumeratorCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReverseQueueTest
|
||||
{
|
||||
private ReverseQueue<char> queue;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
queue = new ReverseQueue<char>(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyQueue()
|
||||
{
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
char unused = queue[0];
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
foreach (var unused in queue)
|
||||
count++;
|
||||
|
||||
Assert.AreEqual(0, count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnqueue()
|
||||
{
|
||||
// Assert correct values and reverse index after enqueueing
|
||||
queue.Enqueue('a');
|
||||
queue.Enqueue('b');
|
||||
queue.Enqueue('c');
|
||||
|
||||
Assert.AreEqual('c', queue[0]);
|
||||
Assert.AreEqual('b', queue[1]);
|
||||
Assert.AreEqual('a', queue[2]);
|
||||
|
||||
// Assert correct values and reverse index after enqueueing beyond initial capacity of 4
|
||||
queue.Enqueue('d');
|
||||
queue.Enqueue('e');
|
||||
queue.Enqueue('f');
|
||||
|
||||
Assert.AreEqual('f', queue[0]);
|
||||
Assert.AreEqual('e', queue[1]);
|
||||
Assert.AreEqual('d', queue[2]);
|
||||
Assert.AreEqual('c', queue[3]);
|
||||
Assert.AreEqual('b', queue[4]);
|
||||
Assert.AreEqual('a', queue[5]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDequeue()
|
||||
{
|
||||
queue.Enqueue('a');
|
||||
queue.Enqueue('b');
|
||||
queue.Enqueue('c');
|
||||
queue.Enqueue('d');
|
||||
queue.Enqueue('e');
|
||||
queue.Enqueue('f');
|
||||
|
||||
// Assert correct item return and no longer in queue after dequeueing
|
||||
Assert.AreEqual('a', queue[5]);
|
||||
var dequeuedItem = queue.Dequeue();
|
||||
|
||||
Assert.AreEqual('a', dequeuedItem);
|
||||
Assert.AreEqual(5, queue.Count);
|
||||
Assert.AreEqual('f', queue[0]);
|
||||
Assert.AreEqual('b', queue[4]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
char unused = queue[5];
|
||||
});
|
||||
|
||||
// Assert correct state after enough enqueues and dequeues to wrap around array (queue.start = 0 again)
|
||||
queue.Enqueue('g');
|
||||
queue.Enqueue('h');
|
||||
queue.Enqueue('i');
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.AreEqual('i', queue[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClear()
|
||||
{
|
||||
queue.Enqueue('a');
|
||||
queue.Enqueue('b');
|
||||
queue.Enqueue('c');
|
||||
queue.Enqueue('d');
|
||||
queue.Enqueue('e');
|
||||
queue.Enqueue('f');
|
||||
|
||||
// Assert queue is empty after clearing
|
||||
queue.Clear();
|
||||
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
char unused = queue[0];
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnumerator()
|
||||
{
|
||||
queue.Enqueue('a');
|
||||
queue.Enqueue('b');
|
||||
queue.Enqueue('c');
|
||||
queue.Enqueue('d');
|
||||
queue.Enqueue('e');
|
||||
queue.Enqueue('f');
|
||||
|
||||
char[] expectedValues = { 'f', 'e', 'd', 'c', 'b', 'a' };
|
||||
int expectedValueIndex = 0;
|
||||
|
||||
// Assert items are enumerated in correct order
|
||||
foreach (var item in queue)
|
||||
{
|
||||
Assert.AreEqual(expectedValues[expectedValueIndex], item);
|
||||
expectedValueIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online
|
||||
private BeatmapSetInfo testBeatmapSet;
|
||||
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
private OnlinePlayBeatmapAvailablilityTracker availablilityTracker;
|
||||
private OnlinePlayBeatmapAvailabilityTracker availabilityTracker;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, GameHost host)
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online
|
||||
Ruleset = { Value = testBeatmapInfo.Ruleset },
|
||||
};
|
||||
|
||||
Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||
Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = selectedItem, }
|
||||
};
|
||||
@@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online
|
||||
});
|
||||
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
|
||||
|
||||
AddStep("recreate tracker", () => Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = selectedItem }
|
||||
});
|
||||
@@ -127,7 +127,7 @@ namespace osu.Game.Tests.Online
|
||||
|
||||
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)
|
||||
{
|
||||
AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke()));
|
||||
AddAssert(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke()));
|
||||
}
|
||||
|
||||
private static BeatmapInfo getTestBeatmapInfo(string archiveFile)
|
||||
|
||||
@@ -9,3 +9,16 @@ osu file format v128
|
||||
|
||||
// Implicit multi-segment
|
||||
32,192,3000,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
|
||||
|
||||
// Last control point duplicated
|
||||
0,0,4000,2,0,B|1:1|2:2|3:3|3:3,2,200
|
||||
|
||||
// Last control point in segment duplicated
|
||||
0,0,5000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200
|
||||
|
||||
// Implicit perfect-curve multi-segment (Should convert to bezier to match stable)
|
||||
0,0,6000,2,0,P|75:145|170:75|170:75|300:145|410:20,1,475,0:0:0:0:
|
||||
|
||||
// Explicit perfect-curve multi-segment (Should not convert to bezier)
|
||||
0,0,7000,2,0,P|75:145|P|170:75|300:145|410:20,1,650,0:0:0:0:
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] // (3 * 0) / (4 * 300) * 300_000 + (0 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] // (3 * 50) / (4 * 300) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] // (3 * 100) / (4 * 300) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 492_857)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] // (3 * 300) / (4 * 300) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] // (3 * 350) / (4 * 350) * 300_000 + (2 / 4) * 700_000
|
||||
[TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] // (3 * 0) / (4 * 10) * 300_000 + 700_000 (max combo 0)
|
||||
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 594)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25)
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new MultiplayerMatchFooter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Height = 50
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,21 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@@ -18,11 +26,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
private MultiplayerMatchSubScreen screen;
|
||||
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
public TestSceneMultiplayerMatchSubScreen()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public new void Setup() => Schedule(() =>
|
||||
{
|
||||
@@ -71,7 +93,48 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddWaitStep("wait", 10);
|
||||
AddUntilStep("wait for join", () => Client.Room != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStartMatchWhileSpectating()
|
||||
{
|
||||
AddStep("set playlist", () =>
|
||||
{
|
||||
Room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("click create button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for room join", () => Client.Room != null);
|
||||
|
||||
AddStep("join other user (ready)", () =>
|
||||
{
|
||||
Client.AddUser(new User { Id = 55 });
|
||||
Client.ChangeUserState(55, MultiplayerUserState.Ready);
|
||||
});
|
||||
|
||||
AddStep("click spectate button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("click ready button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("ready mark invisible", () => !this.ChildrenOfType<StateDisplay>().Single().IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleSpectateState()
|
||||
{
|
||||
AddStep("make user spectating", () => Client.ChangeState(MultiplayerUserState.Spectating));
|
||||
AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCrownChangesStateWhenHostTransferred()
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerReadyButton button;
|
||||
private OnlinePlayBeatmapAvailablilityTracker beatmapTracker;
|
||||
private OnlinePlayBeatmapAvailabilityTracker beatmapTracker;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
|
||||
Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||
Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = selectedItem }
|
||||
});
|
||||
@@ -209,9 +209,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
addClickButtonStep();
|
||||
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
|
||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||
|
||||
AddStep("finish gameplay", () =>
|
||||
{
|
||||
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded);
|
||||
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay);
|
||||
});
|
||||
|
||||
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerSpectateButton spectateButton;
|
||||
private MultiplayerReadyButton readyButton;
|
||||
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
private BeatmapSetInfo importedSet;
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
public TestSceneMultiplayerSpectateButton()
|
||||
{
|
||||
base.Content.Add(content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
});
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
|
||||
var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
|
||||
base.Content.Add(beatmapTracker);
|
||||
Dependencies.Cache(beatmapTracker);
|
||||
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public new void Setup() => Schedule(() =>
|
||||
{
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
|
||||
selectedItem.Value = new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
||||
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset },
|
||||
};
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
spectateButton = new MultiplayerSpectateButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
OnSpectateClick = async () =>
|
||||
{
|
||||
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||
await Client.ToggleSpectate();
|
||||
readyClickOperation.Dispose();
|
||||
}
|
||||
},
|
||||
readyButton = new MultiplayerReadyButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
OnReadyClick = async () =>
|
||||
{
|
||||
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||
|
||||
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||
{
|
||||
await Client.StartMatch();
|
||||
return;
|
||||
}
|
||||
|
||||
await Client.ToggleReady();
|
||||
readyClickOperation.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEnabledWhenRoomOpen()
|
||||
{
|
||||
assertSpectateButtonEnablement(true);
|
||||
}
|
||||
|
||||
[TestCase(MultiplayerUserState.Idle)]
|
||||
[TestCase(MultiplayerUserState.Ready)]
|
||||
public void TestToggleWhenIdle(MultiplayerUserState initialState)
|
||||
{
|
||||
addClickSpectateButtonStep();
|
||||
AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
|
||||
|
||||
addClickSpectateButtonStep();
|
||||
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
[TestCase(MultiplayerRoomState.WaitingForLoad)]
|
||||
[TestCase(MultiplayerRoomState.Playing)]
|
||||
[TestCase(MultiplayerRoomState.Closed)]
|
||||
public void TestDisabledDuringGameplayOrClosed(MultiplayerRoomState roomState)
|
||||
{
|
||||
AddStep($"change user to {roomState}", () => Client.ChangeRoomState(roomState));
|
||||
assertSpectateButtonEnablement(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReadyButtonDisabledWhenHostAndNoReadyUsers()
|
||||
{
|
||||
addClickSpectateButtonStep();
|
||||
assertReadyButtonEnablement(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReadyButtonEnabledWhenHostAndUsersReady()
|
||||
{
|
||||
AddStep("add user", () => Client.AddUser(new User { Id = 55 }));
|
||||
AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready));
|
||||
|
||||
addClickSpectateButtonStep();
|
||||
assertReadyButtonEnablement(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReadyButtonDisabledWhenNotHostAndUsersReady()
|
||||
{
|
||||
AddStep("add user and transfer host", () =>
|
||||
{
|
||||
Client.AddUser(new User { Id = 55 });
|
||||
Client.TransferHost(55);
|
||||
});
|
||||
|
||||
AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready));
|
||||
|
||||
addClickSpectateButtonStep();
|
||||
assertReadyButtonEnablement(false);
|
||||
}
|
||||
|
||||
private void addClickSpectateButtonStep() => AddStep("click spectate button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(spectateButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
|
||||
=> AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||
|
||||
private void assertReadyButtonEnablement(bool shouldBeEnabled)
|
||||
=> AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
protected override bool UseFreshStoragePerRun => true;
|
||||
|
||||
protected override bool CreateNestedActionContainer => false;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
@@ -43,6 +44,20 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
exitViaEscapeAndConfirm();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding
|
||||
/// but should be handled *after* song select).
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestOpenModSelectOverlayUsingAction()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryCountIncrements()
|
||||
{
|
||||
@@ -146,7 +161,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||
|
||||
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
|
||||
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == Game.BackButton));
|
||||
AddUntilStep("Back button is hovered", () => Game.ChildrenOfType<BackButton>().First().Children.Any(c => c.IsHovered));
|
||||
|
||||
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
||||
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
@@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
private BeatmapDetails details;
|
||||
|
||||
private DummyAPIAccess api => (DummyAPIAccess)API;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
@@ -173,6 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
OnlineBeatmapID = 162,
|
||||
});
|
||||
AddStep("set online", () => api.SetState(APIState.Online));
|
||||
AddStep("set offline", () => api.SetState(APIState.Offline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public class TestSceneSongSelectFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
public TestSceneSongSelectFooter()
|
||||
{
|
||||
AddStep("Create footer", () =>
|
||||
{
|
||||
Footer footer;
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
footer = new Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
|
||||
footer.AddButton(new FooterButtonMods(), null);
|
||||
footer.AddButton(new FooterButtonRandom
|
||||
{
|
||||
NextRandom = () => { },
|
||||
PreviousRandom = () => { },
|
||||
}, null);
|
||||
footer.AddButton(new FooterButtonOptions(), null);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public IBeatmap Beatmap { get; }
|
||||
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
@@ -41,8 +39,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public IBeatmap Convert(CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.cancellationToken = cancellationToken;
|
||||
|
||||
// We always operate on a clone of the original beatmap, to not modify it game-wide
|
||||
return ConvertBeatmap(Beatmap.Clone(), cancellationToken);
|
||||
}
|
||||
@@ -55,19 +51,6 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>The converted Beatmap.</returns>
|
||||
protected virtual Beatmap<T> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
return ConvertBeatmap(original);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the conversion of a Beatmap using this Beatmap Converter.
|
||||
/// </summary>
|
||||
/// <param name="original">The un-converted Beatmap.</param>
|
||||
/// <returns>The converted Beatmap.</returns>
|
||||
[Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318
|
||||
protected virtual Beatmap<T> ConvertBeatmap(IBeatmap original)
|
||||
{
|
||||
var beatmap = CreateBeatmap();
|
||||
|
||||
beatmap.BeatmapInfo = original.BeatmapInfo;
|
||||
@@ -121,21 +104,6 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="beatmap">The un-converted Beatmap.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The converted hit object.</returns>
|
||||
protected virtual IEnumerable<T> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
return ConvertHitObject(original, beatmap);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the conversion of a hit object.
|
||||
/// This method is generally executed sequentially for all objects in a beatmap.
|
||||
/// </summary>
|
||||
/// <param name="original">The hit object to convert.</param>
|
||||
/// <param name="beatmap">The un-converted Beatmap.</param>
|
||||
/// <returns>The converted hit object.</returns>
|
||||
[Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318
|
||||
protected virtual IEnumerable<T> ConvertHitObject(HitObject original, IBeatmap beatmap) => Enumerable.Empty<T>();
|
||||
protected virtual IEnumerable<T> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) => Enumerable.Empty<T>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,11 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public class BeatmapStatistic
|
||||
{
|
||||
[Obsolete("Use CreateIcon instead")] // can be removed 20210203
|
||||
public IconUsage Icon = FontAwesome.Regular.QuestionCircle;
|
||||
|
||||
/// <summary>
|
||||
/// A function to create the icon for display purposes. Use default icons available via <see cref="BeatmapStatisticIcon"/> whenever possible for conformity.
|
||||
/// </summary>
|
||||
@@ -20,12 +15,5 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public string Content;
|
||||
public string Name;
|
||||
|
||||
public BeatmapStatistic()
|
||||
{
|
||||
#pragma warning disable 618
|
||||
CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.7f) };
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (hitObject is IHasPath path)
|
||||
{
|
||||
addPathData(writer, path, position);
|
||||
writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true));
|
||||
writer.Write(getSampleBank(hitObject.Samples));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -329,7 +329,26 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (point.Type.Value != null)
|
||||
{
|
||||
if (point.Type.Value != lastType)
|
||||
// We've reached a new (explicit) segment!
|
||||
|
||||
// Explicit segments have a new format in which the type is injected into the middle of the control point string.
|
||||
// To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
|
||||
// One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
|
||||
bool needsExplicitSegment = point.Type.Value != lastType || point.Type.Value == PathType.PerfectCurve;
|
||||
|
||||
// Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
|
||||
// Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
|
||||
if (i > 1)
|
||||
{
|
||||
// We need to use the absolute control point position to determine equality, otherwise floating point issues may arise.
|
||||
Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position.Value;
|
||||
Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position.Value;
|
||||
|
||||
if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y)
|
||||
needsExplicitSegment = true;
|
||||
}
|
||||
|
||||
if (needsExplicitSegment)
|
||||
{
|
||||
switch (point.Type.Value)
|
||||
{
|
||||
@@ -401,15 +420,15 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime}{suffix}"));
|
||||
}
|
||||
|
||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false)
|
||||
{
|
||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||
LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)normalBank)}:"));
|
||||
sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)addBank)}"));
|
||||
sb.Append(FormattableString.Invariant($"{(int)normalBank}:"));
|
||||
sb.Append(FormattableString.Invariant($"{(int)addBank}"));
|
||||
|
||||
if (!banksOnly)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public override void PlayHoverSample()
|
||||
{
|
||||
sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08);
|
||||
sampleHover.Frequency.Value = 0.98 + RNG.NextDouble(0.04);
|
||||
sampleHover.Play();
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Soft,
|
||||
|
||||
[Description("-toolbar")]
|
||||
Toolbar
|
||||
Toolbar,
|
||||
|
||||
[Description("-songselect")]
|
||||
SongSelect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +64,20 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
protected override void ReloadMappings()
|
||||
{
|
||||
var defaults = DefaultKeyBindings.ToList();
|
||||
|
||||
if (ruleset != null && !ruleset.ID.HasValue)
|
||||
// if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
|
||||
// fallback to defaults instead.
|
||||
KeyBindings = DefaultKeyBindings;
|
||||
KeyBindings = defaults;
|
||||
else
|
||||
KeyBindings = store.Query(ruleset?.ID, variant).ToList();
|
||||
{
|
||||
KeyBindings = store.Query(ruleset?.ID, variant)
|
||||
// this ordering is important to ensure that we read entries from the database in the order
|
||||
// enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise
|
||||
// have been eaten by the music controller due to query order.
|
||||
.OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace osu.Game.Input.Bindings
|
||||
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
||||
{
|
||||
private readonly Drawable handler;
|
||||
private InputManager parentInputManager;
|
||||
|
||||
public GlobalActionContainer(OsuGameBase game)
|
||||
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
||||
@@ -21,7 +22,18 @@ namespace osu.Game.Input.Bindings
|
||||
handler = game;
|
||||
}
|
||||
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings);
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
parentInputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||
.Concat(EditorKeyBindings)
|
||||
.Concat(InGameKeyBindings)
|
||||
.Concat(SongSelectKeyBindings)
|
||||
.Concat(AudioControlKeyBindings);
|
||||
|
||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||
{
|
||||
@@ -74,6 +86,14 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
|
||||
};
|
||||
|
||||
public IEnumerable<KeyBinding> SongSelectKeyBindings => new[]
|
||||
{
|
||||
new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection),
|
||||
new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom),
|
||||
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
|
||||
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions)
|
||||
};
|
||||
|
||||
public IEnumerable<KeyBinding> AudioControlKeyBindings => new[]
|
||||
{
|
||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume),
|
||||
@@ -91,8 +111,20 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(InputKey.F3, GlobalAction.MusicPlay)
|
||||
};
|
||||
|
||||
protected override IEnumerable<Drawable> KeyBindingInputQueue =>
|
||||
handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler);
|
||||
protected override IEnumerable<Drawable> KeyBindingInputQueue
|
||||
{
|
||||
get
|
||||
{
|
||||
// To ensure the global actions are handled with priority, this GlobalActionContainer is actually placed after game content.
|
||||
// It does not contain children as expected, so we need to forward the NonPositionalInputQueue from the parent input manager to correctly
|
||||
// allow the whole game to handle these actions.
|
||||
|
||||
// An eventual solution to this hack is to create localised action containers for individual components like SongSelect, but this will take some rearranging.
|
||||
var inputQueue = parentInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue;
|
||||
|
||||
return handler != null ? inputQueue.Prepend(handler) : inputQueue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GlobalAction
|
||||
@@ -204,5 +236,18 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[Description("Toggle in-game interface")]
|
||||
ToggleInGameInterface,
|
||||
|
||||
// Song select keybindings
|
||||
[Description("Toggle Mod Select")]
|
||||
ToggleModSelection,
|
||||
|
||||
[Description("Random")]
|
||||
SelectNextRandom,
|
||||
|
||||
[Description("Rewind")]
|
||||
SelectPreviousRandom,
|
||||
|
||||
[Description("Beatmap Options")]
|
||||
ToggleBeatmapOptions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (newState == MultiplayerUserState.Spectating)
|
||||
return Task.CompletedTask; // Not supported yet.
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,5 +55,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// The user is currently viewing results. This is a reserved state, and is set by the server.
|
||||
/// </summary>
|
||||
Results,
|
||||
|
||||
/// <summary>
|
||||
/// The user is currently spectating this room.
|
||||
/// </summary>
|
||||
Spectating
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +249,33 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the <see cref="LocalUser"/>'s spectating state.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If a toggle of the spectating state is not valid at this time.</exception>
|
||||
public async Task ToggleSpectate()
|
||||
{
|
||||
var localUser = LocalUser;
|
||||
|
||||
if (localUser == null)
|
||||
return;
|
||||
|
||||
switch (localUser.State)
|
||||
{
|
||||
case MultiplayerUserState.Idle:
|
||||
case MultiplayerUserState.Ready:
|
||||
await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false);
|
||||
return;
|
||||
|
||||
case MultiplayerUserState.Spectating:
|
||||
await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false);
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task TransferHost(int userId);
|
||||
|
||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||
|
||||
@@ -23,13 +23,17 @@ namespace osu.Game.Online
|
||||
|
||||
private readonly string placeholderMessage;
|
||||
|
||||
private Placeholder placeholder;
|
||||
private Drawable placeholder;
|
||||
|
||||
private const double transform_duration = 300;
|
||||
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of an online view container.
|
||||
/// </summary>
|
||||
/// <param name="placeholderMessage">The message to display when not logged in. If empty, no button will display.</param>
|
||||
public OnlineViewContainer(string placeholderMessage)
|
||||
{
|
||||
this.placeholderMessage = placeholderMessage;
|
||||
@@ -40,10 +44,10 @@ namespace osu.Game.Online
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
Content,
|
||||
placeholder = new LoginPlaceholder(placeholderMessage),
|
||||
placeholder = string.IsNullOrEmpty(placeholderMessage) ? Empty() : new LoginPlaceholder(placeholderMessage),
|
||||
LoadingSpinner = new LoadingSpinner
|
||||
{
|
||||
Alpha = 0,
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ namespace osu.Game.Online.Rooms
|
||||
/// This differs from a regular download tracking composite as this accounts for the
|
||||
/// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap.
|
||||
/// </summary>
|
||||
public class OnlinePlayBeatmapAvailablilityTracker : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||
public class OnlinePlayBeatmapAvailabilityTracker : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||
{
|
||||
public readonly IBindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API;
|
||||
@@ -11,11 +12,13 @@ namespace osu.Game.Online.Solo
|
||||
public class CreateSoloScoreRequest : APIRequest<APIScoreToken>
|
||||
{
|
||||
private readonly int beatmapId;
|
||||
private readonly int rulesetId;
|
||||
private readonly string versionHash;
|
||||
|
||||
public CreateSoloScoreRequest(int beatmapId, string versionHash)
|
||||
public CreateSoloScoreRequest(int beatmapId, int rulesetId, string versionHash)
|
||||
{
|
||||
this.beatmapId = beatmapId;
|
||||
this.rulesetId = rulesetId;
|
||||
this.versionHash = versionHash;
|
||||
}
|
||||
|
||||
@@ -24,9 +27,10 @@ namespace osu.Game.Online.Solo
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
req.AddParameter("version_hash", versionHash);
|
||||
req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture));
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"solo/{beatmapId}/scores";
|
||||
protected override string Target => $@"beatmaps/{beatmapId}/solo/scores";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,6 @@ namespace osu.Game.Online.Solo
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"solo/{beatmapId}/scores/{scoreId}";
|
||||
protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
@@ -879,13 +878,6 @@ namespace osu.Game
|
||||
return component;
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// forward any unhandled mouse scroll events to the volume control.
|
||||
volume.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (introScreen == null) return false;
|
||||
|
||||
@@ -308,17 +308,18 @@ namespace osu.Game
|
||||
|
||||
AddInternal(RulesetConfigCache);
|
||||
|
||||
MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
GlobalActionContainer globalBindings;
|
||||
|
||||
MenuCursorContainer.Child = globalBindings = new GlobalActionContainer(this)
|
||||
var mainContent = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
|
||||
MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both },
|
||||
// to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
|
||||
globalBindings = new GlobalActionContainer(this)
|
||||
};
|
||||
|
||||
base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer));
|
||||
MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
|
||||
|
||||
KeyBindingStore.Register(globalBindings);
|
||||
dependencies.Cache(globalBindings);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
Add(new DefaultBindingsSubsection(manager));
|
||||
Add(new AudioControlKeyBindingsSubsection(manager));
|
||||
Add(new SongSelectKeyBindingSubsection(manager));
|
||||
Add(new InGameKeyBindingsSubsection(manager));
|
||||
Add(new EditorKeyBindingsSubsection(manager));
|
||||
}
|
||||
@@ -36,6 +37,17 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
|
||||
{
|
||||
protected override string Header => "Song Select";
|
||||
|
||||
public SongSelectKeyBindingSubsection(GlobalActionContainer manager)
|
||||
: base(null)
|
||||
{
|
||||
Defaults = manager.SongSelectKeyBindings;
|
||||
}
|
||||
}
|
||||
|
||||
private class InGameKeyBindingsSubsection : KeyBindingsSubsection
|
||||
{
|
||||
protected override string Header => "In Game";
|
||||
|
||||
@@ -57,13 +57,6 @@ namespace osu.Game.Overlays.Settings
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use Current instead")] // Can be removed 20210406
|
||||
public Bindable<T> Bindable
|
||||
{
|
||||
get => Current;
|
||||
set => Current = value;
|
||||
}
|
||||
|
||||
public virtual Bindable<T> Current
|
||||
{
|
||||
get => controlWithCurrent.Current;
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
@@ -17,6 +18,13 @@ namespace osu.Game.Overlays.Volume
|
||||
public bool OnPressed(GlobalAction action) =>
|
||||
ActionRequested?.Invoke(action) ?? false;
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// forward any unhandled mouse scroll events to the volume control.
|
||||
ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnScroll(GlobalAction action, float amount, bool isPrecise) =>
|
||||
ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public abstract class DifficultyCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// The length of each strain section.
|
||||
/// </summary>
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
private readonly Ruleset ruleset;
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
|
||||
@@ -71,32 +66,14 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
|
||||
var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList();
|
||||
|
||||
double sectionLength = SectionLength * clockRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
|
||||
foreach (DifficultyHitObject h in difficultyHitObjects)
|
||||
foreach (var hitObject in difficultyHitObjects)
|
||||
{
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
foreach (Skill s in skills)
|
||||
{
|
||||
s.SaveCurrentPeak();
|
||||
s.StartNewSectionFrom(currentSectionEnd / clockRate);
|
||||
}
|
||||
|
||||
currentSectionEnd += sectionLength;
|
||||
skill.ProcessInternal(hitObject);
|
||||
}
|
||||
|
||||
foreach (Skill s in skills)
|
||||
s.Process(h);
|
||||
}
|
||||
|
||||
// The peak strain will not be saved for the last section in the above loop
|
||||
foreach (Skill s in skills)
|
||||
s.SaveCurrentPeak();
|
||||
|
||||
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
||||
public readonly HitObject LastObject;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of time elapsed between <see cref="BaseObject"/> and <see cref="LastObject"/>.
|
||||
/// Amount of time elapsed between <see cref="BaseObject"/> and <see cref="LastObject"/>, adjusted by clockrate.
|
||||
/// </summary>
|
||||
public readonly double DeltaTime;
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -11,123 +9,52 @@ using osu.Game.Rulesets.Mods;
|
||||
namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
||||
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
||||
/// A bare minimal abstract skill for fully custom skill implementations.
|
||||
/// </summary>
|
||||
public abstract class Skill
|
||||
{
|
||||
/// <summary>
|
||||
/// The peak strain for each <see cref="DifficultyCalculator.SectionLength"/> section of the beatmap.
|
||||
/// </summary>
|
||||
public IReadOnlyList<double> StrainPeaks => strainPeaks;
|
||||
|
||||
/// <summary>
|
||||
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
|
||||
/// </summary>
|
||||
protected abstract double SkillMultiplier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines how quickly strain decays for the given skill.
|
||||
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
||||
/// </summary>
|
||||
protected abstract double StrainDecayBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The weight by which each strain value decays.
|
||||
/// </summary>
|
||||
protected virtual double DecayWeight => 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DifficultyHitObject"/>s that were processed previously. They can affect the strain values of the following objects.
|
||||
/// </summary>
|
||||
protected readonly LimitedCapacityStack<DifficultyHitObject> Previous = new LimitedCapacityStack<DifficultyHitObject>(2); // Contained objects not used yet
|
||||
protected readonly ReverseQueue<DifficultyHitObject> Previous;
|
||||
|
||||
/// <summary>
|
||||
/// The current strain level.
|
||||
/// Number of previous <see cref="DifficultyHitObject"/>s to keep inside the <see cref="Previous"/> queue.
|
||||
/// </summary>
|
||||
protected double CurrentStrain { get; private set; } = 1;
|
||||
protected virtual int HistoryLength => 1;
|
||||
|
||||
/// <summary>
|
||||
/// Mods for use in skill calculations.
|
||||
/// </summary>
|
||||
protected IReadOnlyList<Mod> Mods => mods;
|
||||
|
||||
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
|
||||
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
|
||||
private readonly Mod[] mods;
|
||||
|
||||
protected Skill(Mod[] mods)
|
||||
{
|
||||
this.mods = mods;
|
||||
Previous = new ReverseQueue<DifficultyHitObject>(HistoryLength + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
|
||||
/// </summary>
|
||||
public void Process(DifficultyHitObject current)
|
||||
internal void ProcessInternal(DifficultyHitObject current)
|
||||
{
|
||||
CurrentStrain *= strainDecay(current.DeltaTime);
|
||||
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
|
||||
while (Previous.Count > HistoryLength)
|
||||
Previous.Dequeue();
|
||||
|
||||
currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
|
||||
Process(current);
|
||||
|
||||
Previous.Push(current);
|
||||
Previous.Enqueue(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
|
||||
/// Process a <see cref="DifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public void SaveCurrentPeak()
|
||||
{
|
||||
if (Previous.Count > 0)
|
||||
strainPeaks.Add(currentSectionPeak);
|
||||
}
|
||||
/// <param name="current">The <see cref="DifficultyHitObject"/> to process.</param>
|
||||
protected abstract void Process(DifficultyHitObject current);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial strain level for a new section.
|
||||
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
|
||||
/// </summary>
|
||||
/// <param name="time">The beginning of the new section in milliseconds, adjusted by clockrate.</param>
|
||||
public void StartNewSectionFrom(double time)
|
||||
{
|
||||
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
|
||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||
if (Previous.Count > 0)
|
||||
currentSectionPeak = GetPeakStrain(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the peak strain at a point in time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the peak strain at, adjusted by clockrate.</param>
|
||||
/// <returns>The peak strain.</returns>
|
||||
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the calculated difficulty value representing all processed <see cref="DifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
public double DifficultyValue()
|
||||
{
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
// We're sorting from highest to lowest strain.
|
||||
foreach (double strain in strainPeaks.OrderByDescending(d => d))
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= DecayWeight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
|
||||
/// </summary>
|
||||
protected abstract double StrainValueOf(DifficultyHitObject current);
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||
public abstract double DifficultyValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
||||
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
||||
/// </summary>
|
||||
public abstract class StrainSkill : Skill
|
||||
{
|
||||
/// <summary>
|
||||
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
|
||||
/// </summary>
|
||||
protected abstract double SkillMultiplier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines how quickly strain decays for the given skill.
|
||||
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
|
||||
/// </summary>
|
||||
protected abstract double StrainDecayBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The weight by which each strain value decays.
|
||||
/// </summary>
|
||||
protected virtual double DecayWeight => 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// The current strain level.
|
||||
/// </summary>
|
||||
protected double CurrentStrain { get; private set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The length of each strain section.
|
||||
/// </summary>
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
|
||||
|
||||
private double currentSectionEnd;
|
||||
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
|
||||
protected StrainSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
|
||||
/// </summary>
|
||||
protected sealed override void Process(DifficultyHitObject current)
|
||||
{
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
if (Previous.Count == 0)
|
||||
currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
|
||||
while (current.StartTime > currentSectionEnd)
|
||||
{
|
||||
saveCurrentPeak();
|
||||
startNewSectionFrom(currentSectionEnd);
|
||||
currentSectionEnd += SectionLength;
|
||||
}
|
||||
|
||||
CurrentStrain *= strainDecay(current.DeltaTime);
|
||||
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
|
||||
|
||||
currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
|
||||
/// </summary>
|
||||
private void saveCurrentPeak()
|
||||
{
|
||||
strainPeaks.Add(currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial strain level for a new section.
|
||||
/// </summary>
|
||||
/// <param name="time">The beginning of the new section in milliseconds.</param>
|
||||
private void startNewSectionFrom(double time)
|
||||
{
|
||||
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
|
||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||
currentSectionPeak = GetPeakStrain(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the peak strain at a point in time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the peak strain at.</param>
|
||||
/// <returns>The peak strain.</returns>
|
||||
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
|
||||
/// including the peak of the current section.
|
||||
/// </summary>
|
||||
public IEnumerable<double> GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
|
||||
/// </summary>
|
||||
public sealed override double DifficultyValue()
|
||||
{
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
// We're sorting from highest to lowest strain.
|
||||
foreach (double strain in GetCurrentStrainPeaks().OrderByDescending(d => d))
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= DecayWeight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
|
||||
/// </summary>
|
||||
protected abstract double StrainValueOf(DifficultyHitObject current);
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// An indexed stack with limited depth. Indexing starts at the top of the stack.
|
||||
/// </summary>
|
||||
public class LimitedCapacityStack<T> : IEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of elements in the stack.
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
private readonly T[] array;
|
||||
private readonly int capacity;
|
||||
private int marker; // Marks the position of the most recently added item.
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="LimitedCapacityStack{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The number of items the stack can hold.</param>
|
||||
public LimitedCapacityStack(int capacity)
|
||||
{
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
|
||||
this.capacity = capacity;
|
||||
array = new T[capacity];
|
||||
marker = capacity; // Set marker to the end of the array, outside of the indexed range by one.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the item at an index in the stack.
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the item to retrieve. The top of the stack is returned at index 0.</param>
|
||||
public T this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i < 0 || i > Count - 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(i));
|
||||
|
||||
i += marker;
|
||||
if (i > capacity - 1)
|
||||
i -= capacity;
|
||||
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an item to this <see cref="LimitedCapacityStack{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to push.</param>
|
||||
public void Push(T item)
|
||||
{
|
||||
// Overwrite the oldest item instead of shifting every item by one with every addition.
|
||||
if (marker == 0)
|
||||
marker = capacity - 1;
|
||||
else
|
||||
--marker;
|
||||
|
||||
array[marker] = item;
|
||||
|
||||
if (Count < capacity)
|
||||
++Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator which enumerates items in the history starting from the most recently added one.
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = marker; i < capacity; ++i)
|
||||
yield return array[i];
|
||||
|
||||
if (Count == capacity)
|
||||
{
|
||||
for (int i = 0; i < marker; ++i)
|
||||
yield return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// An indexed queue where items are indexed beginning from the most recently enqueued item.
|
||||
/// Enqueuing an item pushes all existing indexes up by one and inserts the item at index 0.
|
||||
/// Dequeuing an item removes the item from the highest index and returns it.
|
||||
/// </summary>
|
||||
public class ReverseQueue<T> : IEnumerable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of elements in the <see cref="ReverseQueue{T}"/>.
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
private T[] items;
|
||||
private int capacity;
|
||||
private int start;
|
||||
|
||||
public ReverseQueue(int initialCapacity)
|
||||
{
|
||||
if (initialCapacity <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(initialCapacity));
|
||||
|
||||
items = new T[initialCapacity];
|
||||
capacity = initialCapacity;
|
||||
start = 0;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the item at an index in the <see cref="ReverseQueue{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item to retrieve. The most recently enqueued item is at index 0.</param>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index > Count - 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
int reverseIndex = Count - 1 - index;
|
||||
return items[(start + reverseIndex) % capacity];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an item to this <see cref="ReverseQueue{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to enqueue.</param>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (Count == capacity)
|
||||
{
|
||||
// Double the buffer size
|
||||
var buffer = new T[capacity * 2];
|
||||
|
||||
// Copy items to new queue
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
buffer[i] = items[(start + i) % capacity];
|
||||
}
|
||||
|
||||
// Replace array with new buffer
|
||||
items = buffer;
|
||||
capacity *= 2;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
items[(start + Count) % capacity] = item;
|
||||
Count++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues the least recently enqueued item from the <see cref="ReverseQueue{T}"/> and returns it.
|
||||
/// </summary>
|
||||
/// <returns>The item dequeued from the <see cref="ReverseQueue{T}"/>.</returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
var item = items[start];
|
||||
start = (start + 1) % capacity;
|
||||
Count--;
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the <see cref="ReverseQueue{T}"/> of all items.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
start = 0;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator which enumerates items in the <see cref="ReverseQueue{T}"/> starting from the most recently enqueued item.
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator(this);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private ReverseQueue<T> reverseQueue;
|
||||
private int currentIndex;
|
||||
|
||||
internal Enumerator(ReverseQueue<T> reverseQueue)
|
||||
{
|
||||
this.reverseQueue = reverseQueue;
|
||||
currentIndex = -1; // The first MoveNext() should bring the iterator to 0
|
||||
}
|
||||
|
||||
public bool MoveNext() => ++currentIndex < reverseQueue.Count;
|
||||
|
||||
public void Reset() => currentIndex = -1;
|
||||
|
||||
public readonly T Current => reverseQueue[currentIndex];
|
||||
|
||||
readonly object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
reverseQueue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,18 +28,6 @@ namespace osu.Game.Rulesets.Judgements
|
||||
/// </summary>
|
||||
protected const double DEFAULT_MAX_HEALTH_INCREASE = 0.05;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="Judgement"/> should affect the current combo.
|
||||
/// </summary>
|
||||
[Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328
|
||||
public virtual bool AffectsCombo => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="Judgement"/> should be counted as base (combo) or bonus score.
|
||||
/// </summary>
|
||||
[Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328
|
||||
public virtual bool IsBonus => !AffectsCombo;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="HitResult"/> that can be achieved.
|
||||
/// </summary>
|
||||
@@ -181,7 +169,7 @@ namespace osu.Game.Rulesets.Judgements
|
||||
return 300;
|
||||
|
||||
case HitResult.Perfect:
|
||||
return 350;
|
||||
return 315;
|
||||
|
||||
case HitResult.SmallBonus:
|
||||
return SMALL_BONUS_SCORE;
|
||||
|
||||
@@ -11,7 +11,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@@ -736,24 +735,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
if (!Result.HasResult)
|
||||
throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}.");
|
||||
|
||||
// Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements.
|
||||
// Can be removed 20210328
|
||||
if (Result.Judgement.MaxResult == HitResult.IgnoreHit)
|
||||
{
|
||||
HitResult originalType = Result.Type;
|
||||
|
||||
if (Result.Type == HitResult.Miss)
|
||||
Result.Type = HitResult.IgnoreMiss;
|
||||
else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect)
|
||||
Result.Type = HitResult.IgnoreHit;
|
||||
|
||||
if (Result.Type != originalType)
|
||||
{
|
||||
Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n"
|
||||
+ $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2021-03-28 onwards.", level: LogLevel.Important);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
||||
@@ -139,15 +139,6 @@ namespace osu.Game.Rulesets.Objects
|
||||
}
|
||||
|
||||
protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
// ReSharper disable once MethodSupportsCancellation (https://youtrack.jetbrains.com/issue/RIDER-44520)
|
||||
#pragma warning disable 618
|
||||
CreateNestedHitObjects();
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
[Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318
|
||||
protected virtual void CreateNestedHitObjects()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -336,9 +336,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
while (++endIndex < vertices.Length - endPointLength)
|
||||
{
|
||||
// Keep incrementing while an implicit segment doesn't need to be started
|
||||
if (vertices[endIndex].Position.Value != vertices[endIndex - 1].Position.Value)
|
||||
continue;
|
||||
|
||||
// The last control point of each segment is not allowed to start a new implicit segment.
|
||||
if (endIndex == vertices.Length - endPointLength - 1)
|
||||
continue;
|
||||
|
||||
// Force a type on the last point, and return the current control point set as a segment.
|
||||
vertices[endIndex - 1].Type.Value = type;
|
||||
yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex);
|
||||
|
||||
@@ -156,6 +156,39 @@ namespace osu.Game.Rulesets.Objects
|
||||
return interpolateVertices(indexOfDistance(d), d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the control points belonging to the same segment as the one given.
|
||||
/// The first point has a PathType which all other points inherit.
|
||||
/// </summary>
|
||||
/// <param name="controlPoint">One of the control points in the segment.</param>
|
||||
/// <returns></returns>
|
||||
public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
|
||||
{
|
||||
bool found = false;
|
||||
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
|
||||
|
||||
foreach (PathControlPoint point in ControlPoints)
|
||||
{
|
||||
if (point.Type.Value != null)
|
||||
{
|
||||
if (!found)
|
||||
pointsInCurrentSegment.Clear();
|
||||
else
|
||||
{
|
||||
pointsInCurrentSegment.Add(point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pointsInCurrentSegment.Add(point);
|
||||
|
||||
if (point == controlPoint)
|
||||
found = true;
|
||||
}
|
||||
|
||||
return pointsInCurrentSegment;
|
||||
}
|
||||
|
||||
private void invalidate()
|
||||
{
|
||||
pathCache.Invalidate();
|
||||
|
||||
@@ -346,12 +346,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
score.HitEvents = hitEvents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="HitWindows"/> for this processor.
|
||||
/// </summary>
|
||||
[Obsolete("Method is now unused.")] // Can be removed 20210328
|
||||
public virtual HitWindows CreateHitWindows() => new HitWindows();
|
||||
}
|
||||
|
||||
public enum ScoringMode
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
@@ -91,6 +92,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
},
|
||||
ticks = new TimelineTickDisplay(),
|
||||
controlPoints = new TimelineControlPointDisplay(),
|
||||
new Box
|
||||
{
|
||||
Name = "zero marker",
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Origin = Anchor.TopCentre,
|
||||
Colour = colours.YellowDarker,
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -16,6 +18,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
@@ -35,7 +38,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private Bindable<HitObject> placement;
|
||||
private SelectionBlueprint placementBlueprint;
|
||||
|
||||
private readonly Box backgroundBox;
|
||||
private SelectableAreaBackground backgroundBox;
|
||||
|
||||
// we only care about checking vertical validity.
|
||||
// this allows selecting and dragging selections before time=0.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
float localY = ToLocalSpace(screenSpacePos).Y;
|
||||
return DrawRectangle.Top <= localY && DrawRectangle.Bottom >= localY;
|
||||
}
|
||||
|
||||
public TimelineBlueprintContainer(HitObjectComposer composer)
|
||||
: base(composer)
|
||||
@@ -45,12 +56,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Height = 0.6f;
|
||||
}
|
||||
|
||||
AddInternal(backgroundBox = new Box
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(backgroundBox = new SelectableAreaBackground
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.1f,
|
||||
Colour = Color4.Black
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,6 +208,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private class SelectableAreaBackground : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Alpha = 0.1f;
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
// fade out over intro time, outside the valid time bounds.
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 200,
|
||||
Origin = Anchor.TopRight,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White),
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal class TimelineSelectionHandler : SelectionHandler
|
||||
{
|
||||
// for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation
|
||||
|
||||
@@ -5,8 +5,8 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
@@ -18,15 +18,13 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private LabelledSliderBar<float> approachRateSlider;
|
||||
private LabelledSliderBar<float> overallDifficultySlider;
|
||||
|
||||
public override LocalisableString Title => "Difficulty";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Difficulty settings"
|
||||
},
|
||||
circleSizeSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Object Size",
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
@@ -17,27 +21,27 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
/// <summary>
|
||||
/// A labelled textbox which reveals an inline file chooser when clicked.
|
||||
/// </summary>
|
||||
internal class FileChooserLabelledTextBox : LabelledTextBox
|
||||
internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles
|
||||
{
|
||||
private readonly string[] handledExtensions;
|
||||
public IEnumerable<string> HandledExtensions => handledExtensions;
|
||||
|
||||
/// <summary>
|
||||
/// The target container to display the file chooser in.
|
||||
/// </summary>
|
||||
public Container Target;
|
||||
|
||||
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||
private readonly Bindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private SectionsContainer<SetupSection> sectionsContainer { get; set; }
|
||||
|
||||
public FileChooserLabelledTextBox()
|
||||
public FileChooserLabelledTextBox(params string[] handledExtensions)
|
||||
{
|
||||
currentFile.BindValueChanged(onFileSelected);
|
||||
}
|
||||
|
||||
private void onFileSelected(ValueChangedEvent<FileInfo> file)
|
||||
{
|
||||
if (file.NewValue == null)
|
||||
return;
|
||||
|
||||
Target.Clear();
|
||||
Current.Value = file.NewValue.FullName;
|
||||
this.handledExtensions = handledExtensions;
|
||||
}
|
||||
|
||||
protected override OsuTextBox CreateTextBox() =>
|
||||
@@ -54,7 +58,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
FileSelector fileSelector;
|
||||
|
||||
Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, ResourcesSection.AudioExtensions)
|
||||
Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, handledExtensions)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 400,
|
||||
@@ -64,6 +68,37 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
sectionsContainer.ScrollTo(fileSelector);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
game.RegisterImportHandler(this);
|
||||
currentFile.BindValueChanged(onFileSelected);
|
||||
}
|
||||
|
||||
private void onFileSelected(ValueChangedEvent<FileInfo> file)
|
||||
{
|
||||
if (file.NewValue == null)
|
||||
return;
|
||||
|
||||
Target.Clear();
|
||||
Current.Value = file.NewValue.FullName;
|
||||
}
|
||||
|
||||
Task ICanAcceptFiles.Import(params string[] paths)
|
||||
{
|
||||
Schedule(() => currentFile.Value = new FileInfo(paths.First()));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException();
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
game.UnregisterImportHandler(this);
|
||||
}
|
||||
|
||||
internal class FileChooserOsuTextBox : OsuTextBox
|
||||
{
|
||||
public Action OnFocused;
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
@@ -17,15 +17,13 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private LabelledTextBox creatorTextBox;
|
||||
private LabelledTextBox difficultyTextBox;
|
||||
|
||||
public override LocalisableString Title => "Metadata";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Beatmap metadata"
|
||||
},
|
||||
artistTextBox = new LabelledTextBox
|
||||
{
|
||||
Label = "Artist",
|
||||
|
||||
@@ -1,40 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
internal class ResourcesSection : SetupSection, ICanAcceptFiles
|
||||
internal class ResourcesSection : SetupSection
|
||||
{
|
||||
private LabelledTextBox audioTrackTextBox;
|
||||
private Container backgroundSpriteContainer;
|
||||
private LabelledTextBox backgroundTextBox;
|
||||
|
||||
public IEnumerable<string> HandledExtensions => ImageExtensions.Concat(AudioExtensions);
|
||||
|
||||
public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" };
|
||||
|
||||
public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" };
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
public override LocalisableString Title => "Resources";
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
@@ -48,70 +33,66 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
[Resolved(canBeNull: true)]
|
||||
private Editor editor { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private SetupScreenHeader header { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Container audioTrackFileChooserContainer = new Container
|
||||
Container audioTrackFileChooserContainer = createFileChooserContainer();
|
||||
Container backgroundFileChooserContainer = createFileChooserContainer();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png")
|
||||
{
|
||||
Label = "Background",
|
||||
PlaceholderText = "Click to select a background image",
|
||||
Current = { Value = working.Value.Metadata.BackgroundFile },
|
||||
Target = backgroundFileChooserContainer,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
backgroundFileChooserContainer,
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg")
|
||||
{
|
||||
Label = "Audio Track",
|
||||
PlaceholderText = "Click to select a track",
|
||||
Current = { Value = working.Value.Metadata.AudioFile },
|
||||
Target = audioTrackFileChooserContainer,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
audioTrackFileChooserContainer,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
backgroundTextBox.Current.BindValueChanged(backgroundChanged);
|
||||
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
|
||||
}
|
||||
|
||||
private static Container createFileChooserContainer() =>
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundSpriteContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Resources"
|
||||
},
|
||||
audioTrackTextBox = new FileChooserLabelledTextBox
|
||||
{
|
||||
Label = "Audio Track",
|
||||
PlaceholderText = "Click to select a track",
|
||||
Current = { Value = working.Value.Metadata.AudioFile },
|
||||
Target = audioTrackFileChooserContainer,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
audioTrackFileChooserContainer,
|
||||
};
|
||||
|
||||
updateBackgroundSprite();
|
||||
|
||||
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
|
||||
}
|
||||
|
||||
Task ICanAcceptFiles.Import(params string[] paths)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
var firstFile = new FileInfo(paths.First());
|
||||
|
||||
if (ImageExtensions.Contains(firstFile.Extension))
|
||||
{
|
||||
ChangeBackgroundImage(firstFile.FullName);
|
||||
}
|
||||
else if (AudioExtensions.Contains(firstFile.Extension))
|
||||
{
|
||||
audioTrackTextBox.Text = firstFile.FullName;
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
game.RegisterImportHandler(this);
|
||||
}
|
||||
|
||||
public bool ChangeBackgroundImage(string path)
|
||||
{
|
||||
var info = new FileInfo(path);
|
||||
@@ -134,17 +115,11 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
}
|
||||
|
||||
working.Value.Metadata.BackgroundFile = info.Name;
|
||||
updateBackgroundSprite();
|
||||
header.Background.UpdateBackground();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
game?.UnregisterImportHandler(this);
|
||||
}
|
||||
|
||||
public bool ChangeAudioTrack(string path)
|
||||
{
|
||||
var info = new FileInfo(path);
|
||||
@@ -174,45 +149,16 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
return true;
|
||||
}
|
||||
|
||||
private void backgroundChanged(ValueChangedEvent<string> filePath)
|
||||
{
|
||||
if (!ChangeBackgroundImage(filePath.NewValue))
|
||||
backgroundTextBox.Current.Value = filePath.OldValue;
|
||||
}
|
||||
|
||||
private void audioTrackChanged(ValueChangedEvent<string> filePath)
|
||||
{
|
||||
if (!ChangeAudioTrack(filePath.NewValue))
|
||||
audioTrackTextBox.Current.Value = filePath.OldValue;
|
||||
}
|
||||
|
||||
private void updateBackgroundSprite()
|
||||
{
|
||||
LoadComponentAsync(new BeatmapBackgroundSprite(working.Value)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
}, background =>
|
||||
{
|
||||
if (background.Texture != null)
|
||||
backgroundSpriteContainer.Child = background;
|
||||
else
|
||||
{
|
||||
backgroundSpriteContainer.Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Colours.GreySeafoamDarker,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24))
|
||||
{
|
||||
Text = "Drag image here to set beatmap background!",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.X,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
background.FadeInFromZero(500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,24 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
public class SetupScreen : EditorScreen
|
||||
{
|
||||
public const int HORIZONTAL_PADDING = 100;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider;
|
||||
|
||||
[Cached]
|
||||
private SectionsContainer<SetupSection> sections = new SectionsContainer<SetupSection>();
|
||||
|
||||
[Cached]
|
||||
private SetupScreenHeader header = new SetupScreenHeader();
|
||||
|
||||
public SetupScreen()
|
||||
: base(EditorScreenMode.SongSetup)
|
||||
{
|
||||
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -41,12 +49,12 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.GreySeafoamDark,
|
||||
Colour = ColourProvider.Dark4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new SectionsContainer<SetupSection>
|
||||
sections = new SectionsContainer<SetupSection>
|
||||
{
|
||||
FixedHeader = new SetupScreenHeader(),
|
||||
FixedHeader = header,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new SetupSection[]
|
||||
{
|
||||
@@ -60,19 +68,4 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal class SetupScreenHeader : OverlayHeader
|
||||
{
|
||||
protected override OverlayTitle CreateTitle() => new SetupScreenTitle();
|
||||
|
||||
private class SetupScreenTitle : OverlayTitle
|
||||
{
|
||||
public SetupScreenTitle()
|
||||
{
|
||||
Title = "beatmap setup";
|
||||
Description = "change general settings of your beatmap";
|
||||
IconTexture = "Icons/Hexacons/social";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
internal class SetupScreenHeader : OverlayHeader
|
||||
{
|
||||
public SetupScreenHeaderBackground Background { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private SectionsContainer<SetupSection> sections { get; set; }
|
||||
|
||||
private SetupScreenTabControl tabControl;
|
||||
|
||||
protected override OverlayTitle CreateTitle() => new SetupScreenTitle();
|
||||
|
||||
protected override Drawable CreateContent() => new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
// reverse flow is used to ensure that the tab control's expandable bars extend over the background chooser.
|
||||
Child = new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
tabControl = new SetupScreenTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 30
|
||||
},
|
||||
Background = new SetupScreenHeaderBackground
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 120
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
tabControl.AccentColour = colourProvider.Highlight1;
|
||||
tabControl.BackgroundColour = colourProvider.Dark5;
|
||||
|
||||
foreach (var section in sections)
|
||||
tabControl.AddItem(section);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue);
|
||||
tabControl.Current.BindValueChanged(section =>
|
||||
{
|
||||
if (section.NewValue != sections.SelectedSection.Value)
|
||||
sections.ScrollTo(section.NewValue);
|
||||
});
|
||||
}
|
||||
|
||||
private class SetupScreenTitle : OverlayTitle
|
||||
{
|
||||
public SetupScreenTitle()
|
||||
{
|
||||
Title = "beatmap setup";
|
||||
Description = "change general settings of your beatmap";
|
||||
IconTexture = "Icons/Hexacons/social";
|
||||
}
|
||||
}
|
||||
|
||||
internal class SetupScreenTabControl : OverlayTabControl<SetupSection>
|
||||
{
|
||||
private readonly Box background;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => background.Colour;
|
||||
set => background.Colour = value;
|
||||
}
|
||||
|
||||
public SetupScreenTabControl()
|
||||
{
|
||||
TabContainer.Margin = new MarginPadding { Horizontal = SetupScreen.HORIZONTAL_PADDING };
|
||||
|
||||
AddInternal(background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 1
|
||||
});
|
||||
}
|
||||
|
||||
protected override TabItem<SetupSection> CreateTabItem(SetupSection value) => new SetupScreenTabItem(value)
|
||||
{
|
||||
AccentColour = AccentColour
|
||||
};
|
||||
|
||||
private class SetupScreenTabItem : OverlayTabItem
|
||||
{
|
||||
public SetupScreenTabItem(SetupSection value)
|
||||
: base(value)
|
||||
{
|
||||
Text.Text = value.Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user