mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 01:49:53 +08:00
Compare commits
527 Commits
+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).
|
||||
|
||||
|
||||
@@ -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
+3
-18
@@ -2,13 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
{
|
||||
@@ -21,26 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
|
||||
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
protected Vector2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = CurrentFrame;
|
||||
|
||||
if (frame == null)
|
||||
return Vector2.Zero;
|
||||
|
||||
Debug.Assert(CurrentTime != null);
|
||||
|
||||
return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
inputs.Add(new MousePositionAbsoluteInput
|
||||
{
|
||||
Position = GamefieldToScreenSpace(Position),
|
||||
Position = GamefieldToScreenSpace(position),
|
||||
});
|
||||
inputs.Add(new ReplayState<EmptyFreeformAction>
|
||||
{
|
||||
|
||||
@@ -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
+3
-18
@@ -2,12 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
{
|
||||
@@ -20,26 +18,13 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
|
||||
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
||||
|
||||
protected Vector2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = CurrentFrame;
|
||||
|
||||
if (frame == null)
|
||||
return Vector2.Zero;
|
||||
|
||||
Debug.Assert(CurrentTime != null);
|
||||
|
||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
inputs.Add(new MousePositionAbsoluteInput
|
||||
{
|
||||
Position = GamefieldToScreenSpace(Position)
|
||||
Position = GamefieldToScreenSpace(position)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.412.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.416.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,6 +7,8 @@ using Android.OS;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game;
|
||||
using osu.Game.Updater;
|
||||
using osu.Game.Utils;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace osu.Android
|
||||
{
|
||||
@@ -72,5 +74,14 @@ namespace osu.Android
|
||||
}
|
||||
|
||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||
|
||||
private class AndroidBatteryInfo : BatteryInfo
|
||||
{
|
||||
public override double ChargeLevel => Battery.ChargeLevel;
|
||||
|
||||
public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
||||
</manifest>
|
||||
@@ -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" />
|
||||
@@ -58,5 +63,8 @@
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
</Project>
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
"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",
|
||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ namespace osu.Desktop
|
||||
/// Allow a maximum of one unhandled exception, per second of execution.
|
||||
/// </summary>
|
||||
/// <param name="arg"></param>
|
||||
/// <returns></returns>
|
||||
private static bool handleException(Exception arg)
|
||||
{
|
||||
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
private void addFrame(double time, float? position = null, bool dashing = false)
|
||||
{
|
||||
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
||||
if (Replay.Frames.Count == 0)
|
||||
Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
|
||||
|
||||
var last = currentFrame;
|
||||
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
||||
Replay.Frames.Add(currentFrame);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Utils;
|
||||
@@ -20,29 +19,14 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
protected float? Position
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = CurrentFrame;
|
||||
|
||||
if (frame == null)
|
||||
return null;
|
||||
|
||||
Debug.Assert(CurrentTime != null);
|
||||
|
||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
if (!Position.HasValue) return;
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
inputs.Add(new CatchReplayState
|
||||
{
|
||||
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
|
||||
CatcherX = Position.Value
|
||||
CatcherX = position
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
/// <summary>
|
||||
/// The number of frames which are generated at the start of a replay regardless of hitobject content.
|
||||
/// </summary>
|
||||
private const int frame_offset = 1;
|
||||
private const int frame_offset = 0;
|
||||
|
||||
[Test]
|
||||
public void TestSingleNote()
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
@@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
||||
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
||||
|
||||
@@ -482,7 +482,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// Retrieves the sample info list at a point in time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||
/// <returns></returns>
|
||||
private IList<HitSampleInfo> sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (PlacementActive)
|
||||
if (PlacementActive == PlacementState.Active)
|
||||
{
|
||||
if (result.Time is double endTime)
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (!PlacementActive)
|
||||
if (PlacementActive == PlacementState.Waiting)
|
||||
Column = result.Playfield as Column;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override string Name => "Mirror";
|
||||
public override string Acronym => "MR";
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override string Description => "Notes are flipped horizontally.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
|
||||
@@ -70,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
}
|
||||
}
|
||||
|
||||
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
||||
if (Replay.Frames.Count == 0)
|
||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
|
||||
|
||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckOffscreenObjectsTest
|
||||
{
|
||||
private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE * 0.5f;
|
||||
|
||||
private CheckOffscreenObjects check;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckOffscreenObjects();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleInCenter()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = playfield_centre // Playfield is 640 x 480.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Assert.That(check.Run(beatmap), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleNearEdge()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(5, 5)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Assert.That(check.Run(beatmap), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleNearEdgeStackedOffscreen()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(5, 5),
|
||||
StackHeight = 5
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenCircle(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCircleOffscreen()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(0, 0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenCircle(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderInCenter()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(420, 240),
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(-100, 0))
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Assert.That(check.Run(beatmap), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderNearEdge()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = playfield_centre,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Assert.That(check.Run(beatmap), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderNearEdgeStackedOffscreen()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = playfield_centre,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||
}),
|
||||
StackHeight = 5
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenSlider(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderOffscreenStart()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(0, 0),
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||
new PathControlPoint(playfield_centre)
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenSlider(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderOffscreenEnd()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = playfield_centre,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||
new PathControlPoint(-playfield_centre)
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenSlider(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderOffscreenPath()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = playfield_centre,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
// Circular arc shoots over the top of the screen.
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve),
|
||||
new PathControlPoint(new Vector2(-100, -200)),
|
||||
new PathControlPoint(new Vector2(100, -200))
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertOffscreenSlider(beatmap);
|
||||
}
|
||||
|
||||
private void assertOffscreenCircle(IBeatmap beatmap)
|
||||
{
|
||||
var issues = check.Run(beatmap).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle);
|
||||
}
|
||||
|
||||
private void assertOffscreenSlider(IBeatmap beatmap)
|
||||
{
|
||||
var issues = check.Run(beatmap).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public class TestSceneOsuEditorSelectInvalidPath : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestSelectDoesNotModify()
|
||||
{
|
||||
Slider slider = new Slider { StartTime = 0, Position = new Vector2(320, 40) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.PerfectCurve),
|
||||
new PathControlPoint(new Vector2(-100, 0)),
|
||||
new PathControlPoint(new Vector2(100, 20))
|
||||
};
|
||||
|
||||
int preSelectVersion = -1;
|
||||
AddStep("add slider", () =>
|
||||
{
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
preSelectVersion = slider.Path.Version.Value;
|
||||
});
|
||||
|
||||
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||
|
||||
AddAssert("slider same path", () => slider.Path.Version.Value == preSelectVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneSliderLengthValidity : TestSceneOsuEditor
|
||||
{
|
||||
private OsuPlayfield playfield;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||
AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDraggingStartingPointRemainsValid()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(100, 0)),
|
||||
};
|
||||
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
});
|
||||
|
||||
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
moveMouse(new Vector2(300));
|
||||
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
double distanceBefore = 0;
|
||||
|
||||
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||
|
||||
moveMouse(new Vector2(300, 300));
|
||||
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
moveMouse(new Vector2(350, 300));
|
||||
moveMouse(new Vector2(400, 300));
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDraggingEndingPointRemainsValid()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(100, 0)),
|
||||
};
|
||||
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
});
|
||||
|
||||
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
moveMouse(new Vector2(300));
|
||||
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
double distanceBefore = 0;
|
||||
|
||||
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||
|
||||
moveMouse(new Vector2(400, 300));
|
||||
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
moveMouse(new Vector2(350, 300));
|
||||
moveMouse(new Vector2(300, 300));
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a control point is deleted which results in the slider becoming so short it can't exist,
|
||||
/// for simplicity delete the slider rather than having it in an invalid state.
|
||||
///
|
||||
/// Eventually we may need to change this, based on user feedback. I think it's likely enough of
|
||||
/// an edge case that we won't get many complaints, though (and there's always the undo button).
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDeletingPointCausesSliderDeletion()
|
||||
{
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
Slider slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.PerfectCurve),
|
||||
new PathControlPoint(new Vector2(100, 0)),
|
||||
new PathControlPoint(new Vector2(0, 10))
|
||||
};
|
||||
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
});
|
||||
|
||||
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
moveMouse(new Vector2(400, 300));
|
||||
AddStep("delete second point", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
|
||||
AddAssert("ensure object deleted", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a scale operation is performed where a single slider is the only thing selected, the path's shape will change.
|
||||
/// If the scale results in the path becoming too short, further mouse movement in the same direction will not change the shape.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestScalingSliderTooSmallRemainsValid()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300, 200) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(0, 50)),
|
||||
new PathControlPoint(new Vector2(0, 100))
|
||||
};
|
||||
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
});
|
||||
|
||||
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
moveMouse(new Vector2(300));
|
||||
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
double distanceBefore = 0;
|
||||
|
||||
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||
|
||||
AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<SelectionBoxDragHandle>().Skip(1).First()));
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
moveMouse(new Vector2(300, 300));
|
||||
moveMouse(new Vector2(300, 250));
|
||||
moveMouse(new Vector2(300, 200));
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||
}
|
||||
|
||||
private void moveMouse(Vector2 pos) =>
|
||||
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||
}
|
||||
}
|
||||
@@ -41,9 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addClickStep(MouseButton.Left);
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertLength(0);
|
||||
assertControlPointType(0, PathType.Linear);
|
||||
assertPlaced(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -276,6 +274,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)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSliderScaling : TestSceneOsuEditor
|
||||
{
|
||||
private OsuPlayfield playfield;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||
AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScalingLinearSlider()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||
|
||||
PathControlPoint[] points =
|
||||
{
|
||||
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(100, 0)),
|
||||
};
|
||||
|
||||
slider.Path = new SliderPath(points);
|
||||
EditorBeatmap.Add(slider);
|
||||
});
|
||||
|
||||
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
moveMouse(new Vector2(300));
|
||||
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
double distanceBefore = 0;
|
||||
|
||||
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||
|
||||
AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<SelectionBoxDragHandle>().Skip(1).First()));
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
moveMouse(new Vector2(300, 300));
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||
}
|
||||
|
||||
private void moveMouse(Vector2 pos) =>
|
||||
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
private void runSpmTest(Mod mod)
|
||||
{
|
||||
SpinnerSpmCounter spmCounter = null;
|
||||
SpinnerSpmCalculator spmCalculator = null;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
@@ -53,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1
|
||||
});
|
||||
|
||||
AddUntilStep("fetch SPM counter", () =>
|
||||
AddUntilStep("fetch SPM calculator", () =>
|
||||
{
|
||||
spmCounter = this.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
||||
return spmCounter != null;
|
||||
spmCalculator = this.ChildrenOfType<SpinnerSpmCalculator>().SingleOrDefault();
|
||||
return spmCalculator != null;
|
||||
});
|
||||
|
||||
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCounter.SpinsPerMinute, 477, 5));
|
||||
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () =>
|
||||
{
|
||||
var counter = Player.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
||||
return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1);
|
||||
var counter = Player.ChildrenOfType<SpinnerSpmCalculator>().SingleOrDefault();
|
||||
return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
{
|
||||
@@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
return null;
|
||||
}
|
||||
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||
|
||||
public event Action SourceChanged
|
||||
{
|
||||
|
||||
@@ -4,13 +4,22 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing.Input;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@@ -21,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Cached]
|
||||
private GameplayBeatmap gameplayBeatmap;
|
||||
|
||||
private ClickingCursorContainer lastContainer;
|
||||
private OsuCursorContainer lastContainer;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
@@ -48,12 +57,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
|
||||
Scheduler.AddOnce(recreate);
|
||||
Scheduler.AddOnce(() => loadContent(false));
|
||||
});
|
||||
|
||||
AddStep("test cursor container", recreate);
|
||||
|
||||
void recreate() => SetContents(() => new OsuInputManager(new OsuRuleset().RulesetInfo) { Child = new OsuCursorContainer() });
|
||||
AddStep("test cursor container", () => loadContent(false));
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
@@ -68,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
|
||||
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
||||
|
||||
AddStep("load content", loadContent);
|
||||
AddStep("load content", () => loadContent());
|
||||
|
||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
||||
|
||||
@@ -82,18 +89,46 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
|
||||
}
|
||||
|
||||
private void loadContent()
|
||||
[Test]
|
||||
public void TestTopLeftOrigin()
|
||||
{
|
||||
SetContents(() => new MovingCursorInputManager
|
||||
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
|
||||
}
|
||||
|
||||
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null)
|
||||
{
|
||||
SetContents(() =>
|
||||
{
|
||||
Child = lastContainer = new ClickingCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
}
|
||||
var inputManager = automated ? (InputManager)new MovingCursorInputManager() : new OsuInputManager(new OsuRuleset().RulesetInfo);
|
||||
var skinContainer = skinProvider?.Invoke() ?? new SkinProvidingContainer(null);
|
||||
|
||||
lastContainer = automated ? new ClickingCursorContainer() : new OsuCursorContainer();
|
||||
|
||||
return inputManager.WithChild(skinContainer.WithChild(lastContainer));
|
||||
});
|
||||
}
|
||||
|
||||
private class TopLeftCursorSkin : ISkin
|
||||
{
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case OsuSkinConfiguration osuLookup:
|
||||
if (osuLookup == OsuSkinConfiguration.CursorCentre)
|
||||
return SkinUtils.As<TValue>(new BindableBool(false));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ClickingCursorContainer : OsuCursorContainer
|
||||
{
|
||||
private bool pressed;
|
||||
|
||||
@@ -168,13 +168,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
double estimatedSpm = 0;
|
||||
|
||||
addSeekStep(1000);
|
||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
|
||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
||||
|
||||
addSeekStep(2000);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||
|
||||
addSeekStep(1000);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||
}
|
||||
|
||||
[TestCase(0.5)]
|
||||
@@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("retrieve spinner state", () =>
|
||||
{
|
||||
expectedProgress = drawableSpinner.Progress;
|
||||
expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute;
|
||||
expectedSpm = drawableSpinner.SpinsPerMinute.Value;
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
@@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
addSeekStep(1000);
|
||||
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0));
|
||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
||||
}
|
||||
|
||||
private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay
|
||||
|
||||
@@ -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,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
this.slider = slider;
|
||||
ControlPoint = controlPoint;
|
||||
|
||||
// we don't want to run the path type update on construction as it may inadvertently change the slider.
|
||||
cachePoints(slider);
|
||||
|
||||
slider.Path.Version.BindValueChanged(_ =>
|
||||
{
|
||||
cachePoints(slider);
|
||||
updatePathType();
|
||||
});
|
||||
|
||||
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
@@ -150,6 +164,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 +174,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;
|
||||
}
|
||||
@@ -168,6 +185,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position.Value).ToArray();
|
||||
var oldPosition = slider.Position;
|
||||
var oldStartTime = slider.StartTime;
|
||||
|
||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||
{
|
||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||
@@ -184,10 +205,45 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
else
|
||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||
|
||||
if (!slider.Path.HasValidLength)
|
||||
{
|
||||
for (var i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||
slider.Path.ControlPoints[i].Position.Value = oldControlPoints[i];
|
||||
|
||||
slider.Position = oldPosition;
|
||||
slider.StartTime = oldStartTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
||||
|
||||
/// <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)
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
private PlacementState state;
|
||||
private SliderPlacementState state;
|
||||
private PathControlPoint segmentStart;
|
||||
private PathControlPoint cursor;
|
||||
private int currentSegmentLength;
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
|
||||
};
|
||||
|
||||
setState(PlacementState.Initial);
|
||||
setState(SliderPlacementState.Initial);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -73,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PlacementState.Initial:
|
||||
case SliderPlacementState.Initial:
|
||||
BeginPlacement();
|
||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
break;
|
||||
|
||||
case PlacementState.Body:
|
||||
case SliderPlacementState.Body:
|
||||
updateCursor();
|
||||
break;
|
||||
}
|
||||
@@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PlacementState.Initial:
|
||||
case SliderPlacementState.Initial:
|
||||
beginCurve();
|
||||
break;
|
||||
|
||||
case PlacementState.Body:
|
||||
case SliderPlacementState.Body:
|
||||
if (canPlaceNewControlPoint(out var lastPoint))
|
||||
{
|
||||
// Place a new point by detatching the current cursor.
|
||||
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
if (state == PlacementState.Body && e.Button == MouseButton.Right)
|
||||
if (state == SliderPlacementState.Body && e.Button == MouseButton.Right)
|
||||
endCurve();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
@@ -129,19 +129,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private void beginCurve()
|
||||
{
|
||||
BeginPlacement(commitStart: true);
|
||||
setState(PlacementState.Body);
|
||||
setState(SliderPlacementState.Body);
|
||||
}
|
||||
|
||||
private void endCurve()
|
||||
{
|
||||
updateSlider();
|
||||
EndPlacement(true);
|
||||
EndPlacement(HitObject.Path.HasValidLength);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
updateSlider();
|
||||
|
||||
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||
updatePathType();
|
||||
}
|
||||
|
||||
private void updatePathType()
|
||||
@@ -216,12 +219,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
|
||||
}
|
||||
|
||||
private void setState(PlacementState newState)
|
||||
private void setState(SliderPlacementState newState)
|
||||
{
|
||||
state = newState;
|
||||
}
|
||||
|
||||
private enum PlacementState
|
||||
private enum SliderPlacementState
|
||||
{
|
||||
Initial,
|
||||
Body,
|
||||
|
||||
@@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||
if (controlPoints.Count <= 1)
|
||||
if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength)
|
||||
{
|
||||
placementHandler?.Delete(HitObject);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||
{
|
||||
public class CheckOffscreenObjects : ICheck
|
||||
{
|
||||
// A close approximation for the bounding box of the screen in gameplay on 4:3 aspect ratio.
|
||||
// Uses gameplay space coordinates (512 x 384 playfield / 640 x 480 screen area).
|
||||
// See https://github.com/ppy/osu/pull/12361#discussion_r612199777 for reference.
|
||||
private const int min_x = -67;
|
||||
private const int min_y = -60;
|
||||
private const int max_x = 579;
|
||||
private const int max_y = 428;
|
||||
|
||||
// The amount of milliseconds to step through a slider path at a time
|
||||
// (higher = more performant, but higher false-negative chance).
|
||||
private const int path_step_size = 5;
|
||||
|
||||
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Offscreen hitobjects");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateOffscreenCircle(this),
|
||||
new IssueTemplateOffscreenSlider(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap beatmap)
|
||||
{
|
||||
foreach (var hitobject in beatmap.HitObjects)
|
||||
{
|
||||
switch (hitobject)
|
||||
{
|
||||
case Slider slider:
|
||||
{
|
||||
foreach (var issue in sliderIssues(slider))
|
||||
yield return issue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HitCircle circle:
|
||||
{
|
||||
if (isOffscreen(circle.StackedPosition, circle.Radius))
|
||||
yield return new IssueTemplateOffscreenCircle(this).Create(circle);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Steps through points on the slider to ensure the entire path is on-screen.
|
||||
/// Returns at most one issue.
|
||||
/// </summary>
|
||||
/// <param name="slider">The slider whose path to check.</param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<Issue> sliderIssues(Slider slider)
|
||||
{
|
||||
for (int i = 0; i < slider.Distance; i += path_step_size)
|
||||
{
|
||||
double progress = i / slider.Distance;
|
||||
Vector2 position = slider.StackedPositionAt(progress);
|
||||
|
||||
if (!isOffscreen(position, slider.Radius))
|
||||
continue;
|
||||
|
||||
// `SpanDuration` ensures we don't include reverses.
|
||||
double time = slider.StartTime + progress * slider.SpanDuration;
|
||||
yield return new IssueTemplateOffscreenSlider(this).Create(slider, time);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Above loop may skip the last position in the slider due to step size.
|
||||
if (!isOffscreen(slider.StackedEndPosition, slider.Radius))
|
||||
yield break;
|
||||
|
||||
yield return new IssueTemplateOffscreenSlider(this).Create(slider, slider.EndTime);
|
||||
}
|
||||
|
||||
private bool isOffscreen(Vector2 position, double radius)
|
||||
{
|
||||
return position.X - radius < min_x || position.X + radius > max_x ||
|
||||
position.Y - radius < min_y || position.Y + radius > max_y;
|
||||
}
|
||||
|
||||
public class IssueTemplateOffscreenCircle : IssueTemplate
|
||||
{
|
||||
public IssueTemplateOffscreenCircle(ICheck check)
|
||||
: base(check, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(HitCircle circle) => new Issue(circle, this);
|
||||
}
|
||||
|
||||
public class IssueTemplateOffscreenSlider : IssueTemplate
|
||||
{
|
||||
public IssueTemplateOffscreenSlider(ICheck check)
|
||||
: base(check, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class OsuBeatmapVerifier : IBeatmapVerifier
|
||||
{
|
||||
private readonly List<ICheck> checks = new List<ICheck>
|
||||
{
|
||||
new CheckOffscreenObjects()
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
|
||||
}
|
||||
}
|
||||
@@ -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,12 +201,16 @@ 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.
|
||||
// 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.
|
||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
||||
|
||||
Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height);
|
||||
Vector2 pathRelativeDeltaScale = new Vector2(
|
||||
sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width,
|
||||
sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height);
|
||||
|
||||
Queue<Vector2> oldControlPoints = new Queue<Vector2>();
|
||||
|
||||
@@ -209,11 +220,15 @@ 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);
|
||||
|
||||
if (xInBounds && yInBounds)
|
||||
if (xInBounds && yInBounds && slider.Path.HasValidLength)
|
||||
return;
|
||||
|
||||
foreach (var point in slider.Path.ControlPoints)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
[SettingSource("Roll speed", "Rotations per minute")]
|
||||
public BindableNumber<double> SpinSpeed { get; } = new BindableDouble(0.5)
|
||||
{
|
||||
MinValue = 0.02,
|
||||
MaxValue = 4,
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
[SettingSource("Direction", "The direction of rotation")]
|
||||
public Bindable<RotationDirection> Direction { get; } = new Bindable<RotationDirection>(RotationDirection.Clockwise);
|
||||
|
||||
public override string Name => "Barrel Roll";
|
||||
public override string Acronym => "BR";
|
||||
public override string Description => "The whole playfield is on a wheel!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// scale the playfield to allow all hitobjects to stay within the visible region.
|
||||
drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
@@ -23,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
private const float default_flashlight_size = 180;
|
||||
|
||||
private const double default_follow_delay = 120;
|
||||
|
||||
private OsuFlashlight flashlight;
|
||||
|
||||
public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
|
||||
@@ -35,8 +39,25 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public override void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
base.ApplyToDrawableRuleset(drawableRuleset);
|
||||
|
||||
flashlight.FollowDelay = FollowDelay.Value;
|
||||
}
|
||||
|
||||
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
|
||||
public BindableNumber<double> FollowDelay { get; } = new BindableDouble(default_follow_delay)
|
||||
{
|
||||
MinValue = default_follow_delay,
|
||||
MaxValue = default_follow_delay * 10,
|
||||
Precision = default_follow_delay,
|
||||
};
|
||||
|
||||
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
||||
{
|
||||
public double FollowDelay { private get; set; }
|
||||
|
||||
public OsuFlashlight()
|
||||
{
|
||||
FlashlightSize = new Vector2(0, getSizeFor(0));
|
||||
@@ -50,13 +71,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
const double follow_delay = 120;
|
||||
|
||||
var position = FlashlightPosition;
|
||||
var destination = e.MousePosition;
|
||||
|
||||
FlashlightPosition = Interpolation.ValueAt(
|
||||
Math.Min(Math.Abs(Clock.ElapsedFrameTime), follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
||||
Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out);
|
||||
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Touch Device";
|
||||
public override string Acronym => "TD";
|
||||
public override string Description => "Automatically applied to plays on devices with a touchscreen.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override ModType Type => ModType.System;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result;
|
||||
|
||||
public SpinnerRotationTracker RotationTracker { get; private set; }
|
||||
public SpinnerSpmCounter SpmCounter { get; private set; }
|
||||
|
||||
private SpinnerSpmCalculator spmCalculator;
|
||||
|
||||
private Container<DrawableSpinnerTick> ticks;
|
||||
private PausableSkinnableSound spinningSample;
|
||||
@@ -43,7 +44,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// </summary>
|
||||
public IBindable<double> GainedBonus => gainedBonus;
|
||||
|
||||
private readonly Bindable<double> gainedBonus = new Bindable<double>();
|
||||
private readonly Bindable<double> gainedBonus = new BindableDouble();
|
||||
|
||||
/// <summary>
|
||||
/// The number of spins per minute this spinner is spinning at, for display purposes.
|
||||
/// </summary>
|
||||
public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
|
||||
|
||||
private const double fade_out_duration = 160;
|
||||
|
||||
@@ -63,8 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Origin = Anchor.Centre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
spmCalculator = new SpinnerSpmCalculator
|
||||
{
|
||||
Result = { BindTarget = SpinsPerMinute },
|
||||
},
|
||||
ticks = new Container<DrawableSpinnerTick>(),
|
||||
new AspectContainer
|
||||
{
|
||||
@@ -77,20 +87,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
RotationTracker = new SpinnerRotationTracker(this)
|
||||
}
|
||||
},
|
||||
SpmCounter = new SpinnerSpmCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = 120,
|
||||
Alpha = 0
|
||||
},
|
||||
spinningSample = new PausableSkinnableSound
|
||||
{
|
||||
Volume = { Value = 0 },
|
||||
Looping = true,
|
||||
Frequency = { Value = spinning_sample_initial_frequency }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||
}
|
||||
@@ -161,17 +164,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateStartTimeStateTransforms()
|
||||
{
|
||||
base.UpdateStartTimeStateTransforms();
|
||||
|
||||
if (Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
fadeInCounter();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
{
|
||||
base.UpdateHitStateTransforms(state);
|
||||
@@ -282,22 +274,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
||||
{
|
||||
Result.TimeStarted ??= Time.Current;
|
||||
fadeInCounter();
|
||||
}
|
||||
if (Result.TimeStarted == null && RotationTracker.Tracking)
|
||||
Result.TimeStarted = Time.Current;
|
||||
|
||||
// don't update after end time to avoid the rate display dropping during fade out.
|
||||
// this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
|
||||
if (Time.Current <= HitObject.EndTime)
|
||||
SpmCounter.SetRotation(Result.RateAdjustedRotation);
|
||||
spmCalculator.SetRotation(Result.RateAdjustedRotation);
|
||||
|
||||
updateBonusScore();
|
||||
}
|
||||
|
||||
private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||
|
||||
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
|
||||
|
||||
private int wholeSpins;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -185,6 +185,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
new OsuModTraceable(),
|
||||
new OsuModBarrelRoll(),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
@@ -206,6 +207,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier();
|
||||
|
||||
public override string Description => "osu!";
|
||||
|
||||
public override string ShortName => SHORT_NAME;
|
||||
|
||||
@@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
buttonIndex = 0;
|
||||
|
||||
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
|
||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
||||
|
||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
@@ -21,24 +19,11 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
||||
|
||||
protected Vector2? Position
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = CurrentFrame;
|
||||
|
||||
if (frame == null)
|
||||
return null;
|
||||
|
||||
Debug.Assert(CurrentTime != null);
|
||||
|
||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) });
|
||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||
|
||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) });
|
||||
inputs.Add(new ReplayState<OsuAction> { PressedActions = CurrentFrame?.Actions ?? new List<OsuAction>() });
|
||||
}
|
||||
}
|
||||
|
||||
+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:
|
||||
@@ -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;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
private OsuSpriteText bonusCounter;
|
||||
|
||||
private Container spmContainer;
|
||||
private OsuSpriteText spmCounter;
|
||||
|
||||
public DefaultSpinner()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@@ -46,11 +50,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Numeric.With(size: 24),
|
||||
Y = -120,
|
||||
},
|
||||
spmContainer = new Container
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = 120,
|
||||
Children = new[]
|
||||
{
|
||||
spmCounter = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"0",
|
||||
Font = OsuFont.Numeric.With(size: 24)
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"SPINS PER MINUTE",
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Y = 30
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IBindable<double> gainedBonus;
|
||||
private IBindable<double> spinsPerMinute;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@@ -63,6 +93,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
bonusCounter.FadeOutFromOne(1500);
|
||||
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
||||
});
|
||||
|
||||
spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||
spinsPerMinute.BindValueChanged(spm =>
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void fadeCounterOnTimeStart()
|
||||
{
|
||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-54
@@ -1,77 +1,37 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public class SpinnerSpmCounter : Container
|
||||
public class SpinnerSpmCalculator : Component
|
||||
{
|
||||
private readonly Queue<RotationRecord> records = new Queue<RotationRecord>();
|
||||
private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
|
||||
|
||||
/// <summary>
|
||||
/// The resultant spins per minute value, which is updated via <see cref="SetRotation"/>.
|
||||
/// </summary>
|
||||
public IBindable<double> Result => result;
|
||||
|
||||
private readonly Bindable<double> result = new BindableDouble();
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableSpinner { get; set; }
|
||||
|
||||
private readonly OsuSpriteText spmText;
|
||||
|
||||
public SpinnerSpmCounter()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
spmText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"0",
|
||||
Font = OsuFont.Numeric.With(size: 24)
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"SPINS PER MINUTE",
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Y = 30
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
drawableSpinner.HitObjectApplied += resetState;
|
||||
}
|
||||
|
||||
private double spm;
|
||||
|
||||
public double SpinsPerMinute
|
||||
{
|
||||
get => spm;
|
||||
private set
|
||||
{
|
||||
if (value == spm) return;
|
||||
|
||||
spm = value;
|
||||
spmText.Text = Math.Truncate(value).ToString(@"#0");
|
||||
}
|
||||
}
|
||||
|
||||
private struct RotationRecord
|
||||
{
|
||||
public float Rotation;
|
||||
public double Time;
|
||||
}
|
||||
|
||||
private readonly Queue<RotationRecord> records = new Queue<RotationRecord>();
|
||||
private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
|
||||
|
||||
public void SetRotation(float currentRotation)
|
||||
{
|
||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||
@@ -88,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
|
||||
record = records.Dequeue();
|
||||
|
||||
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||
result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||
}
|
||||
|
||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||
@@ -96,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
private void resetState(DrawableHitObject hitObject)
|
||||
{
|
||||
SpinsPerMinute = 0;
|
||||
result.Value = 0;
|
||||
records.Clear();
|
||||
}
|
||||
|
||||
@@ -107,5 +67,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
if (drawableSpinner != null)
|
||||
drawableSpinner.HitObjectApplied -= resetState;
|
||||
}
|
||||
|
||||
private struct RotationRecord
|
||||
{
|
||||
public float Rotation;
|
||||
public double Time;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
||||
|
||||
InternalChildren = new[]
|
||||
@@ -32,13 +33,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Texture = skin.GetTexture("cursor"),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||
},
|
||||
new NonPlayfieldSprite
|
||||
{
|
||||
Texture = skin.GetTexture("cursormiddle"),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Texture = skin.GetTexture("cursortrail");
|
||||
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
||||
|
||||
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
|
||||
if (disjointTrail)
|
||||
{
|
||||
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||
|
||||
TrailOrigin = centre ? Anchor.Centre : Anchor.TopLeft;
|
||||
Blending = BlendingParameters.Inherit;
|
||||
}
|
||||
else
|
||||
{
|
||||
Blending = BlendingParameters.Additive;
|
||||
}
|
||||
|
||||
if (Texture != null)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
protected const float SPRITE_SCALE = 0.625f;
|
||||
|
||||
private const float spm_hide_offset = 50f;
|
||||
|
||||
protected DrawableSpinner DrawableSpinner { get; private set; }
|
||||
|
||||
private Sprite spin;
|
||||
@@ -35,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
private LegacySpriteText bonusCounter;
|
||||
|
||||
private Sprite spmBackground;
|
||||
private LegacySpriteText spmCounter;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableHitObject, ISkinSource source)
|
||||
{
|
||||
@@ -79,11 +84,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 299,
|
||||
}.With(s => s.Font = s.Font.With(fixedWidth: false)),
|
||||
spmBackground = new Sprite
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopLeft,
|
||||
Texture = source.GetTexture("spinner-rpm"),
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Position = new Vector2(-87, 445 + spm_hide_offset),
|
||||
},
|
||||
spmCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopRight,
|
||||
Scale = new Vector2(SPRITE_SCALE * 0.9f),
|
||||
Position = new Vector2(80, 448 + spm_hide_offset),
|
||||
}.With(s => s.Font = s.Font.With(fixedWidth: false)),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IBindable<double> gainedBonus;
|
||||
private IBindable<double> spinsPerMinute;
|
||||
|
||||
private readonly Bindable<bool> completed = new Bindable<bool>();
|
||||
|
||||
@@ -99,6 +120,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out);
|
||||
});
|
||||
|
||||
spinsPerMinute = DrawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||
spinsPerMinute.BindValueChanged(spm =>
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
completed.BindValueChanged(onCompletedChanged, true);
|
||||
|
||||
DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms;
|
||||
@@ -142,10 +169,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
switch (drawableHitObject)
|
||||
{
|
||||
case DrawableSpinner d:
|
||||
double fadeOutLength = Math.Min(400, d.HitObject.Duration);
|
||||
using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn))
|
||||
{
|
||||
spmBackground.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||
}
|
||||
|
||||
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - fadeOutLength, true))
|
||||
spin.FadeOutFromOne(fadeOutLength);
|
||||
double spinFadeOutLength = Math.Min(400, d.HitObject.Duration);
|
||||
|
||||
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true))
|
||||
spin.FadeOutFromOne(spinFadeOutLength);
|
||||
break;
|
||||
|
||||
case DrawableSpinnerTick d:
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
SliderBorderSize,
|
||||
SliderPathRadius,
|
||||
AllowSliderBallTint,
|
||||
CursorCentre,
|
||||
CursorExpand,
|
||||
CursorRotate,
|
||||
HitCircleOverlayAboveNumber,
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Batches;
|
||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||
@@ -31,6 +32,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private double timeOffset;
|
||||
private float time;
|
||||
|
||||
private Anchor trailOrigin = Anchor.Centre;
|
||||
|
||||
protected Anchor TrailOrigin
|
||||
{
|
||||
get => trailOrigin;
|
||||
set
|
||||
{
|
||||
trailOrigin = value;
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
}
|
||||
|
||||
public CursorTrail()
|
||||
{
|
||||
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
|
||||
@@ -197,6 +210,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||
private Vector2 size;
|
||||
|
||||
private Vector2 originPosition;
|
||||
|
||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||
|
||||
public TrailDrawNode(CursorTrail source)
|
||||
@@ -213,6 +228,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
size = Source.partSize;
|
||||
time = Source.time;
|
||||
|
||||
originPosition = Vector2.Zero;
|
||||
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.x1))
|
||||
originPosition.X = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.x2))
|
||||
originPosition.X = 1f;
|
||||
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.y1))
|
||||
originPosition.Y = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.y2))
|
||||
originPosition.Y = 1f;
|
||||
|
||||
Source.parts.CopyTo(parts, 0);
|
||||
}
|
||||
|
||||
@@ -237,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
|
||||
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||
TexturePosition = textureRect.BottomLeft,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||
@@ -246,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
|
||||
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||
TexturePosition = textureRect.BottomRight,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||
@@ -255,7 +282,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
|
||||
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y),
|
||||
TexturePosition = textureRect.TopRight,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||
@@ -264,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
|
||||
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y),
|
||||
TexturePosition = textureRect.TopLeft,
|
||||
TextureRect = new Vector4(0, 0, 1, 1),
|
||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public OsuPlayfield()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
Add(cursorScaleContainer = new Container
|
||||
{
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
{
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (PlacementActive)
|
||||
if (PlacementActive == PlacementState.Active)
|
||||
{
|
||||
if (result.Time is double dragTime)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
bool hitButton = true;
|
||||
|
||||
Frames.Add(new TaikoReplayFrame(-100000));
|
||||
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
|
||||
|
||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckBackgroundTest
|
||||
{
|
||||
private CheckBackground check;
|
||||
private IBeatmap beatmap;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckBackground();
|
||||
beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Files = new List<BeatmapSetFileInfo>(new[]
|
||||
{
|
||||
new BeatmapSetFileInfo { Filename = "abc123.jpg" }
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundSetAndInFiles()
|
||||
{
|
||||
Assert.That(check.Run(beatmap), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundSetAndNotInFiles()
|
||||
{
|
||||
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||
|
||||
var issues = check.Run(beatmap).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundNotSet()
|
||||
{
|
||||
beatmap.Metadata.BackgroundFile = null;
|
||||
|
||||
var issues = check.Run(beatmap).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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.Online.API;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Mods
|
||||
{
|
||||
[TestFixture]
|
||||
public class ModSettingsEqualityComparison
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
|
||||
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var apiMod1 = new APIMod(mod1);
|
||||
var apiMod2 = new APIMod(mod2);
|
||||
var apiMod3 = new APIMod(mod3);
|
||||
|
||||
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod3));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public override string Name => nameof(ModA);
|
||||
public override string Acronym => nameof(ModA);
|
||||
public override string Description => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
|
||||
@@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
private class ModB : Mod
|
||||
{
|
||||
public override string Name => nameof(ModB);
|
||||
public override string Description => string.Empty;
|
||||
public override string Acronym => nameof(ModB);
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public override string Name => nameof(ModC);
|
||||
public override string Acronym => nameof(ModC);
|
||||
public override string Description => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
|
||||
@@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public override string Name => $"Incompatible With {nameof(ModA)}";
|
||||
public override string Acronym => $"Incompatible With {nameof(ModA)}";
|
||||
public override string Description => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
|
||||
@@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
||||
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
||||
public override string Description => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
|
||||
|
||||
@@ -20,27 +20,14 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
handler = new TestInputHandler(replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new TestReplayFrame(0),
|
||||
new TestReplayFrame(1000),
|
||||
new TestReplayFrame(2000),
|
||||
new TestReplayFrame(3000, true),
|
||||
new TestReplayFrame(4000, true),
|
||||
new TestReplayFrame(5000, true),
|
||||
new TestReplayFrame(7000, true),
|
||||
new TestReplayFrame(8000),
|
||||
}
|
||||
HasReceivedAllFrames = false
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalPlayback()
|
||||
{
|
||||
Assert.IsNull(handler.CurrentFrame);
|
||||
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
setReplayFrames();
|
||||
|
||||
setTime(0, 0);
|
||||
confirmCurrentFrame(0);
|
||||
@@ -107,6 +94,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestIntroTime()
|
||||
{
|
||||
setReplayFrames();
|
||||
|
||||
setTime(-1000, -1000);
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
@@ -123,6 +112,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestBasicRewind()
|
||||
{
|
||||
setReplayFrames();
|
||||
|
||||
setTime(2800, 0);
|
||||
setTime(2800, 1000);
|
||||
setTime(2800, 2000);
|
||||
@@ -133,34 +124,35 @@ namespace osu.Game.Tests.NonVisual
|
||||
// pivot without crossing a frame boundary
|
||||
setTime(2700, 2700);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
confirmNextFrame(3);
|
||||
|
||||
// cross current frame boundary; should not yet update frame
|
||||
setTime(1980, 1980);
|
||||
// cross current frame boundary
|
||||
setTime(1980, 2000);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
confirmNextFrame(3);
|
||||
|
||||
setTime(1200, 1200);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
confirmCurrentFrame(1);
|
||||
confirmNextFrame(2);
|
||||
|
||||
// ensure each frame plays out until start
|
||||
setTime(-500, 1000);
|
||||
confirmCurrentFrame(1);
|
||||
confirmNextFrame(0);
|
||||
confirmNextFrame(2);
|
||||
|
||||
setTime(-500, 0);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(null);
|
||||
confirmNextFrame(1);
|
||||
|
||||
setTime(-500, -500);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(null);
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRewindInsideImportantSection()
|
||||
{
|
||||
setReplayFrames();
|
||||
fastForwardToPoint(3000);
|
||||
|
||||
setTime(4000, 4000);
|
||||
@@ -168,12 +160,12 @@ namespace osu.Game.Tests.NonVisual
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(3);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(3);
|
||||
@@ -187,46 +179,127 @@ namespace osu.Game.Tests.NonVisual
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(4000, null);
|
||||
setTime(4000, 4000);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(3);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
confirmNextFrame(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRewindOutOfImportantSection()
|
||||
{
|
||||
setReplayFrames();
|
||||
fastForwardToPoint(3500);
|
||||
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3200, null);
|
||||
// next frame doesn't change even though direction reversed, because of important section.
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3000, null);
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(2800, 2800);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReplayStreaming()
|
||||
{
|
||||
// no frames are arrived yet
|
||||
setTime(0, null);
|
||||
setTime(1000, null);
|
||||
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting for the first frame");
|
||||
|
||||
replay.Frames.Add(new TestReplayFrame(0));
|
||||
replay.Frames.Add(new TestReplayFrame(1000));
|
||||
|
||||
// should always play from beginning
|
||||
setTime(1000, 0);
|
||||
confirmCurrentFrame(0);
|
||||
Assert.IsFalse(handler.WaitingForFrame, "Should not be waiting yet");
|
||||
setTime(1000, 1000);
|
||||
confirmCurrentFrame(1);
|
||||
confirmNextFrame(null);
|
||||
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting");
|
||||
|
||||
// cannot seek beyond the last frame
|
||||
setTime(1500, null);
|
||||
confirmCurrentFrame(1);
|
||||
|
||||
setTime(-100, 0);
|
||||
confirmCurrentFrame(0);
|
||||
|
||||
// can seek to the point before the first frame, however
|
||||
setTime(-100, -100);
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
|
||||
fastForwardToPoint(1000);
|
||||
setTime(3000, null);
|
||||
replay.Frames.Add(new TestReplayFrame(2000));
|
||||
confirmCurrentFrame(1);
|
||||
setTime(1000, 1000);
|
||||
setTime(3000, 2000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleFramesSameTime()
|
||||
{
|
||||
replay.Frames.Add(new TestReplayFrame(0));
|
||||
replay.Frames.Add(new TestReplayFrame(0));
|
||||
replay.Frames.Add(new TestReplayFrame(1000));
|
||||
replay.Frames.Add(new TestReplayFrame(1000));
|
||||
replay.Frames.Add(new TestReplayFrame(2000));
|
||||
|
||||
// forward direction is prioritized when multiple frames have the same time.
|
||||
setTime(0, 0);
|
||||
setTime(0, 0);
|
||||
|
||||
setTime(2000, 1000);
|
||||
setTime(2000, 1000);
|
||||
|
||||
setTime(1000, 1000);
|
||||
setTime(1000, 1000);
|
||||
setTime(-100, 1000);
|
||||
setTime(-100, 0);
|
||||
setTime(-100, 0);
|
||||
setTime(-100, -100);
|
||||
}
|
||||
|
||||
private void setReplayFrames()
|
||||
{
|
||||
replay.Frames = new List<ReplayFrame>
|
||||
{
|
||||
new TestReplayFrame(0),
|
||||
new TestReplayFrame(1000),
|
||||
new TestReplayFrame(2000),
|
||||
new TestReplayFrame(3000, true),
|
||||
new TestReplayFrame(4000, true),
|
||||
new TestReplayFrame(5000, true),
|
||||
new TestReplayFrame(7000, true),
|
||||
new TestReplayFrame(8000),
|
||||
};
|
||||
replay.HasReceivedAllFrames = true;
|
||||
}
|
||||
|
||||
private void fastForwardToPoint(double destination)
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
if (handler.SetFrameFromTime(destination) == null)
|
||||
var time = handler.SetFrameFromTime(destination);
|
||||
if (time == null || time == destination)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,33 +308,17 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
private void setTime(double set, double? expect)
|
||||
{
|
||||
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
|
||||
Assert.AreEqual(expect, handler.SetFrameFromTime(set), "Unexpected return value");
|
||||
}
|
||||
|
||||
private void confirmCurrentFrame(int? frame)
|
||||
{
|
||||
if (frame.HasValue)
|
||||
{
|
||||
Assert.IsNotNull(handler.CurrentFrame);
|
||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(handler.CurrentFrame);
|
||||
}
|
||||
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame");
|
||||
}
|
||||
|
||||
private void confirmNextFrame(int? frame)
|
||||
{
|
||||
if (frame.HasValue)
|
||||
{
|
||||
Assert.IsNotNull(handler.NextFrame);
|
||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(handler.NextFrame);
|
||||
}
|
||||
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame");
|
||||
}
|
||||
|
||||
private class TestReplayFrame : ReplayFrame
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,296 +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.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class StreamingFramedReplayInputHandlerTest
|
||||
{
|
||||
private Replay replay;
|
||||
private TestInputHandler handler;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
handler = new TestInputHandler(replay = new Replay
|
||||
{
|
||||
HasReceivedAllFrames = false,
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new TestReplayFrame(0),
|
||||
new TestReplayFrame(1000),
|
||||
new TestReplayFrame(2000),
|
||||
new TestReplayFrame(3000, true),
|
||||
new TestReplayFrame(4000, true),
|
||||
new TestReplayFrame(5000, true),
|
||||
new TestReplayFrame(7000, true),
|
||||
new TestReplayFrame(8000),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalPlayback()
|
||||
{
|
||||
Assert.IsNull(handler.CurrentFrame);
|
||||
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
|
||||
setTime(0, 0);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(1);
|
||||
|
||||
// if we hit the first frame perfectly, time should progress to it.
|
||||
setTime(1000, 1000);
|
||||
confirmCurrentFrame(1);
|
||||
confirmNextFrame(2);
|
||||
|
||||
// in between non-important frames should progress based on input.
|
||||
setTime(1200, 1200);
|
||||
confirmCurrentFrame(1);
|
||||
|
||||
setTime(1400, 1400);
|
||||
confirmCurrentFrame(1);
|
||||
|
||||
// progressing beyond the next frame should force time to that frame once.
|
||||
setTime(2200, 2000);
|
||||
confirmCurrentFrame(2);
|
||||
|
||||
// second attempt should progress to input time
|
||||
setTime(2200, 2200);
|
||||
confirmCurrentFrame(2);
|
||||
|
||||
// entering important section
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
|
||||
// cannot progress within
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(3);
|
||||
|
||||
setTime(4000, 4000);
|
||||
confirmCurrentFrame(4);
|
||||
|
||||
// still cannot progress
|
||||
setTime(4500, null);
|
||||
confirmCurrentFrame(4);
|
||||
|
||||
setTime(5200, 5000);
|
||||
confirmCurrentFrame(5);
|
||||
|
||||
// important section AllowedImportantTimeSpan allowance
|
||||
setTime(5200, 5200);
|
||||
confirmCurrentFrame(5);
|
||||
|
||||
setTime(7200, 7000);
|
||||
confirmCurrentFrame(6);
|
||||
|
||||
setTime(7200, null);
|
||||
confirmCurrentFrame(6);
|
||||
|
||||
// exited important section
|
||||
setTime(8200, 8000);
|
||||
confirmCurrentFrame(7);
|
||||
confirmNextFrame(null);
|
||||
|
||||
setTime(8200, null);
|
||||
confirmCurrentFrame(7);
|
||||
confirmNextFrame(null);
|
||||
|
||||
setTime(8400, null);
|
||||
confirmCurrentFrame(7);
|
||||
confirmNextFrame(null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIntroTime()
|
||||
{
|
||||
setTime(-1000, -1000);
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
|
||||
setTime(-500, -500);
|
||||
confirmCurrentFrame(null);
|
||||
confirmNextFrame(0);
|
||||
|
||||
setTime(0, 0);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicRewind()
|
||||
{
|
||||
setTime(2800, 0);
|
||||
setTime(2800, 1000);
|
||||
setTime(2800, 2000);
|
||||
setTime(2800, 2800);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(3);
|
||||
|
||||
// pivot without crossing a frame boundary
|
||||
setTime(2700, 2700);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
|
||||
// cross current frame boundary; should not yet update frame
|
||||
setTime(1980, 1980);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
|
||||
setTime(1200, 1200);
|
||||
confirmCurrentFrame(2);
|
||||
confirmNextFrame(1);
|
||||
|
||||
// ensure each frame plays out until start
|
||||
setTime(-500, 1000);
|
||||
confirmCurrentFrame(1);
|
||||
confirmNextFrame(0);
|
||||
|
||||
setTime(-500, 0);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(null);
|
||||
|
||||
setTime(-500, -500);
|
||||
confirmCurrentFrame(0);
|
||||
confirmNextFrame(null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRewindInsideImportantSection()
|
||||
{
|
||||
fastForwardToPoint(3000);
|
||||
|
||||
setTime(4000, 4000);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(3);
|
||||
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(4000, 4000);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(4500, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(4000, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(5);
|
||||
|
||||
setTime(3500, null);
|
||||
confirmCurrentFrame(4);
|
||||
confirmNextFrame(3);
|
||||
|
||||
setTime(3000, 3000);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRewindOutOfImportantSection()
|
||||
{
|
||||
fastForwardToPoint(3500);
|
||||
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3200, null);
|
||||
// next frame doesn't change even though direction reversed, because of important section.
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(3000, null);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(4);
|
||||
|
||||
setTime(2800, 2800);
|
||||
confirmCurrentFrame(3);
|
||||
confirmNextFrame(2);
|
||||
}
|
||||
|
||||
private void fastForwardToPoint(double destination)
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
if (handler.SetFrameFromTime(destination) == null)
|
||||
return;
|
||||
}
|
||||
|
||||
throw new TimeoutException("Seek was never fulfilled");
|
||||
}
|
||||
|
||||
private void setTime(double set, double? expect)
|
||||
{
|
||||
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
|
||||
}
|
||||
|
||||
private void confirmCurrentFrame(int? frame)
|
||||
{
|
||||
if (frame.HasValue)
|
||||
{
|
||||
Assert.IsNotNull(handler.CurrentFrame);
|
||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(handler.CurrentFrame);
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmNextFrame(int? frame)
|
||||
{
|
||||
if (frame.HasValue)
|
||||
{
|
||||
Assert.IsNotNull(handler.NextFrame);
|
||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(handler.NextFrame);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestReplayFrame : ReplayFrame
|
||||
{
|
||||
public readonly bool IsImportant;
|
||||
|
||||
public TestReplayFrame(double time, bool isImportant = false)
|
||||
: base(time)
|
||||
{
|
||||
IsImportant = isImportant;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
{
|
||||
public TestInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
FrameAccuratePlayback = true;
|
||||
}
|
||||
|
||||
protected override double AllowedImportantTimeSpan => 1000;
|
||||
|
||||
protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user