diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a38e6ef4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,491 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.java] +ij_smart_tabs = true +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = true +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_field_with_annotations = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 1 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = true +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 1000 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = false +ij_java_doc_align_param_comments = false +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = true +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = @*,*,|,java.**,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = true +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_on_demand_import_from_same_package_first = true +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = true +ij_java_method_parameters_right_paren_on_new_line = true +ij_java_method_parameters_wrap = on_every_item +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 10 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = true +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_preserve_module_imports = true +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = true +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = true +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = true +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = true +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_inside_block_braces_when_body_is_present = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.bash,*.sh,*.zsh}] +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.kt,*.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_indent_before_arrow_on_new_line = true +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_elvis = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.har,*.json,*.jsonc,*.mcmeta,.prettierrc}] +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock,uv.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f017c46e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Use lf endings by default. +* text=auto eol=lf + +# Declare text file types just in case +*.java text +*.yml text +*.xml text +*.md text + +# Exclude binary files +*.png binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f24e4164 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/automerge_dependabot.yml b/.github/workflows/automerge_dependabot.yml new file mode 100644 index 00000000..6fe77258 --- /dev/null +++ b/.github/workflows/automerge_dependabot.yml @@ -0,0 +1,55 @@ +name: Auto-merge Dependabot PRs + +on: + workflow_run: + workflows: [ "Pull Request" ] + types: [ completed ] + +jobs: + merge-dependabot: + if: "github.actor == 'dependabot[bot]' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion == 'success'" + runs-on: ubuntu-latest + steps: + # Note: this is directly from GitHub's example for using data from a triggering workflow: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow + - name: 'Download artifact' + uses: actions/github-script@v8 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr_number" + })[0]; + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data)); + + # This might be a useless use of cat, but I'm not sure what shell Actions is going to be running. + - name: Add Pull Number Variable + run: |- + unzip pr_number.zip + echo "PR_NUMBER=$(cat pr_number)" >> "$GITHUB_ENV" + + - name: Approve + uses: hmarr/auto-approve-action@v4.0.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + pull-request-number: "${{ env.PR_NUMBER }}" + - name: Merge + uses: pascalgn/automerge-action@v0.16.4 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + MERGE_LABELS: "dependencies,java" + MERGE_METHOD: "squash" + PULL_REQUEST: "${{ env.PR_NUMBER }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a73e107f..03d2e29b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,86 +2,46 @@ name: OpenInv CI on: push: - create: - types: [tag] - pull_request_target: + branches: + - 'master' + tags-ignore: + - '**' + paths-ignore: + - resource-pack/openinv-legibility-pack/** + # Enable running CI via other Actions, i.e. for drafting releases and handling PRs. + workflow_call: jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout Code - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - - name: Set Up Java - uses: actions/setup-java@v1 + - uses: actions/setup-java@v5 with: - java-version: 1.8 + distribution: 'temurin' + java-version: '21' - # Use cache to speed up build - - name: Cache Maven Repo - uses: actions/cache@v2 - id: cache + # We can't use 'maven' prebuilt cache setup because it requires that the project have a pom file. + # BuildTools installs to Maven local if available, so it's easier to just rely on that. + - name: Cache Spigot dependency + uses: actions/cache@v4 with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + path: | + ~/.m2/repository/org/spigotmc/ + key: ${{ runner.os }}-buildtools-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-buildtools- - # Install Spigot dependencies. - # This script uses Maven to check all required installations and ensure that they are present. - - name: Install Spigot Dependencies - run: . scripts/install_spigot_dependencies.sh + - uses: gradle/actions/setup-gradle@v5 - - name: Build With Maven - run: mvn -e clean package -am -P all + - name: Build with Gradle + run: ./gradlew clean build # Upload artifacts - name: Upload Distributable Jar id: upload-final - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v5 with: name: dist - path: ./target/OpenInv.jar - - name: Upload API Jar - id: upload-api - uses: actions/upload-artifact@v2 - with: - name: api - path: ./api/target/openinvapi*.jar - - release: - name: Create Github Release - needs: [ build ] - if: github.event_name == 'create' && github.event.ref_type == 'tag' - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v2 - - - name: Download Artifacts - uses: actions/download-artifact@v2 - - - name: Generate changelog - run: . scripts/generate_changelog.sh - - - name: Create Release - id: create-release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - body: ${{ env.GENERATED_CHANGELOG }} - draft: true - prerelease: false - - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: ./OpenInv.jar - asset_name: OpenInv.jar - asset_content_type: application/java-archive \ No newline at end of file + path: ./dist/* diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml new file mode 100644 index 00000000..0655b2e0 --- /dev/null +++ b/.github/workflows/draft_release.yml @@ -0,0 +1,36 @@ +name: Draft Github Release + +on: + push: + tags: + - '**' + +jobs: + run-ci: + uses: Jikoo/OpenInv/.github/workflows/ci.yml@master + draft-release: + needs: [ run-ci ] + runs-on: ubuntu-latest + steps: + - name: Download build + uses: actions/download-artifact@v6 + with: + name: dist + path: dist + + - name: Create Release + id: create-release + uses: softprops/action-gh-release@v2.5.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: OpenInv ${{ env.VERSIONED_NAME }} + body: |- + ## Supported server versions + **Paper:** TODO VERSION, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.1 + **Spigot:** TODO VERSION + + TODO HELLO HUMAN, PRESS THE GENERATE CHANGELOG BUTTON PLEASE. + draft: true + prerelease: false + files: ./dist/** diff --git a/.github/workflows/external_release.yml b/.github/workflows/external_release.yml new file mode 100644 index 00000000..a4c4dc76 --- /dev/null +++ b/.github/workflows/external_release.yml @@ -0,0 +1,37 @@ +name: Release to CurseForge + +on: + release: + types: [ released ] + +jobs: + curseforge_release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Fetch Github Release Asset + uses: dsaltares/fetch-gh-release-asset@1.1.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: ${{ github.event.release.id }} + file: OpenInv.jar + + - name: Set CurseForge Variables + run: . scripts/set_curseforge_env.sh "${{ github.event.release.body }}" + + - name: Create CurseForge Release + uses: itsmeow/curseforge-upload@v3 + with: + token: "${{ secrets.CURSEFORGE_TOKEN }}" + project_id: 31432 + game_endpoint: minecraft + file_path: ./OpenInv.jar + display_name: "${{ github.event.release.name }}" + game_versions: "${{ env.CURSEFORGE_MINECRAFT_VERSIONS }}" + release_type: release + changelog_type: markdown + changelog: "${{ github.event.release.body }}" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 00000000..5ad508c1 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,22 @@ +name: Pull Request + +on: + pull_request: + +jobs: + run-ci: + uses: Jikoo/OpenInv/.github/workflows/ci.yml@master + store-dependabot-pr-data: + if: "github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'" + runs-on: ubuntu-latest + steps: + # Note: this is directly from GitHub's example for using data from a triggering workflow: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow + - name: Store Pull Number + run: | + mkdir -p ./pr + echo ${{ github.event.number }} > ./pr/pr_number + - uses: actions/upload-artifact@v5 + with: + name: pr_number + path: pr/ diff --git a/.github/workflows/resource_pack_ci.yml b/.github/workflows/resource_pack_ci.yml new file mode 100644 index 00000000..b4651e16 --- /dev/null +++ b/.github/workflows/resource_pack_ci.yml @@ -0,0 +1,24 @@ +name: Resource Pack CI + +on: + push: + branches: + - 'master' + tags-ignore: + - '**' + paths: + - resource-pack/openinv-legibility-pack/** + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Build resource pack + id: upload-resource-pack + uses: actions/upload-artifact@v5 + with: + name: openinv-legibility-pack + path: ./resource-pack/openinv-legibility-pack/ + compression-level: 9 diff --git a/.gitignore b/.gitignore index b48a4778..27ac0af7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,12 @@ **/.project **/.classpath **/.idea/ +**/.gradle/ **.iml **/target/ **/bin/ **/out/ +**/build/ +**/dist/ **/dependency-reduced-pom.xml **/pom.xml.versionsBackup diff --git a/README.MD b/README.MD deleted file mode 100644 index 66c183ff..00000000 --- a/README.MD +++ /dev/null @@ -1,172 +0,0 @@ -## About -OpenInv is a [Bukkit plugin](https://dev.bukkit.org/bukkit-plugins/openinv/) which allows users to open and edit anyone's inventory or ender chest - online or not! - -## Features -- **OpenInv**: Open anyone's inventory, even if they're offline. - - Read-only mode! No edits allowed! Don't grant the permission `OpenInv.editinv` - - Cross-world support! Don't grant `OpenInv.crossworld` - - No self-opening! Don't grant `OpenInv.openself` - - Drop items as the player! Place items in the unused slots to the right of the armor to drop them -- **OpenEnder**: Open anyone's ender chest, even if they're offline. - - Read-only mode! No edits allowed! Don't grant `OpenInv.editender` - - Cross-world support! Don't grant `OpenInv.crossworld` - - No opening others! Don't grant `OpenInv.openenderall` -- **SilentContainer**: Open containers without displaying an animation or making sound. -- **AnyContainer**: Open containers, even if blocked by ocelots or blocks. - -## Commands -
| Command | -Aliases | -Description | -
|---|---|---|
| /openinv [player] | -oi, inv, open | -Open a player's inventory. If unspecified, will select last player opened or own if none opened previously. | -
| /openender [player] | -oe | -Open a player's ender chest. If unspecified, will select last player opened or own if none opened previously. | -
| /searchinv <item> [minAmount] | -si | -Lists all online players that have a certain item in their inventory. | -
| /searchender <item> [minAmount] | -se | -Lists all online players that have a certain item in their ender chest. | -
| /searchenchant <[enchantment] [MinLevel]> | -searchenchants | -Lists all online players with a specific enchantment. | -
| /anycontainer [check] | -ac, anychest | -Check or toggle the AnyContainer function, allowing opening blocked containers. | -
| /silentcontainer [check] | -sc, silentchest | -Check or toggle the SilentContainer function, allowing opening containers silently. | -
| Node | -Description | -
|---|---|
| OpenInv.* | -Gives permission to use all of OpenInv. | -
| OpenInv.openinv | -Required to use /openinv. | -
| OpenInv.openself | -Required to open own inventory. | -
| OpenInv.editinv | -Required to make changes to open inventories. | -
| OpenInv.openonline | -Allows users to open online players' inventories. For compatibility reasons this is granted by the nodes OpenInv.openinv and OpenInv.openender. | -
| OpenInv.openoffline | -Allows users to open offline players' inventories. For compatibility reasons this is granted by the nodes OpenInv.openinv and OpenInv.openender. | -
| OpenInv.openender | -Required to use /openender. | -
| OpenInv.editender | -Required to make changes to open ender chests. | -
| OpenInv.openenderall | -Allows users to open others' ender chests. Without it, users can only open their own. | -
| OpenInv.exempt | -Prevents the player's inventory being opened by others. | -
| OpenInv.override | -Allows bypassing of the exempt permission. | -
| OpenInv.crossworld | -Allows cross-world usage of /openinv and /openender. | -
| OpenInv.search | -Required to use /searchinv and /searchender. | -
| OpenInv.searchenchant | -Required to use /searchenchant. | -
| OpenInv.anychest | -Required to use /anychest. | -
| OpenInv.any.default | -Cause AnyContainer to be enabled by default. | -
| OpenInv.silent | -Required to use /silentcontainer. | -
| OpenInv.silent.default | -Cause SilentContainer to be enabled by default. | -
| OpenInv.spectate | -Allows users in spectate gamemode to edit inventories. | -
- * Note: This method is potentially very heavily blocking. It should not ever be called on the - * main thread, and if it is, a stack trace will be displayed alerting server owners to the - * call. - * - * @param name the name of the Player - * @return the OfflinePlayer with the closest matching name or null if no players have ever logged in - */ - default @Nullable OfflinePlayer matchPlayer(@NotNull String name) { - - // Warn if called on the main thread - if we resort to searching offline players, this may take several seconds. - if (Bukkit.getServer().isPrimaryThread()) { - this.getLogger().warning("Call to OpenInv#matchPlayer made on the main thread!"); - this.getLogger().warning("This can cause the server to hang, potentially severely."); - this.getLogger().warning("Trace:"); - for (StackTraceElement element : new Throwable().fillInStackTrace().getStackTrace()) { - this.getLogger().warning(element.toString()); - } - } - - OfflinePlayer player; - - try { - UUID uuid = UUID.fromString(name); - player = Bukkit.getOfflinePlayer(uuid); - // Ensure player is a real player, otherwise return null - if (player.hasPlayedBefore() || player.isOnline()) { - return player; - } - } catch (IllegalArgumentException ignored) { - // Not a UUID - } - - // Ensure name is valid if server is in online mode to avoid unnecessary searching - if (Bukkit.getServer().getOnlineMode() && !name.matches("[a-zA-Z0-9_]{3,16}")) { - return null; - } - - player = Bukkit.getServer().getPlayerExact(name); - - if (player != null) { - return player; - } - - player = Bukkit.getServer().getOfflinePlayer(name); - - if (player.hasPlayedBefore()) { - return player; - } - - player = Bukkit.getServer().getPlayer(name); - - if (player != null) { - return player; - } - - float bestMatch = 0; - for (OfflinePlayer offline : Bukkit.getServer().getOfflinePlayers()) { - if (offline.getName() == null) { - // Loaded by UUID only, name has never been looked up. - continue; - } - - float currentMatch = StringMetric.compareJaroWinkler(name, offline.getName()); - - if (currentMatch == 1.0F) { - return offline; - } - - if (currentMatch > bestMatch) { - bestMatch = currentMatch; - player = offline; - } - } - - // Only null if no players have played ever, otherwise even the worst match will do. - return player; - } - - /** - * Open an ISpecialInventory for a Player. - * - * @param player the Player - * @param inventory the ISpecialInventory - * @return the InventoryView for the opened ISpecialInventory - */ - @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory); - - /** - * Check the configuration value for whether or not OpenInv displays a notification to the user - * when a container is activated with AnyChest. - * - * @return true unless configured otherwise - */ - boolean notifyAnyChest(); - - /** - * Check the configuration value for whether or not OpenInv displays a notification to the user - * when a container is activated with SilentChest. - * - * @return true unless configured otherwise - */ - boolean notifySilentChest(); - - /** - * Mark a Player as no longer in use by a Plugin to allow OpenInv to remove it from the cache - * when eligible. - * - * @param player the Player - * @param plugin the Plugin no longer holding a reference to the Player - * @throws IllegalStateException if the server version is unsupported - */ - void releasePlayer(@NotNull Player player, @NotNull Plugin plugin); - - /** - * Mark a Player as in use by a Plugin to prevent it from being removed from the cache. Used to - * prevent issues with multiple copies of the same Player being loaded such as lishid#49. - * Changes made to loaded copies overwrite changes to the others when saved, leading to - * duplication bugs and more. - *
- * When finished with the Player object, be sure to call {@link #releasePlayer(Player, Plugin)} - * to prevent the cache from keeping it stored until the plugin is disabled. - *
- * When using a Player object from OpenInv, you must handle the Player coming online, replacing - * your Player reference with the Player from the PlayerJoinEvent. In addition, you must change - * any values in the Player to reflect any unsaved alterations to the existing Player which do - * not affect the inventory or ender chest contents. - *
- * OpenInv only saves player data when unloading a Player from the cache, and then only if - * {@link #disableSaving()} returns false. If you are making changes that OpenInv does not cause - * to persist when a Player logs in as noted above, it is suggested that you manually call - * {@link Player#saveData()} when releasing your reference to ensure your changes persist. - * - * @param player the Player - * @param plugin the Plugin holding the reference to the Player - * @throws IllegalStateException if the server version is unsupported - */ - void retainPlayer(@NotNull Player player, @NotNull Plugin plugin); - - /** - * Sets a player's AnyChest setting. - * - * @param offline the OfflinePlayer - * @param status the status - * @throws IllegalStateException if the server version is unsupported - */ - void setPlayerAnyChestStatus(@NotNull OfflinePlayer offline, boolean status); - - /** - * Sets a player's SilentChest setting. - * - * @param offline the OfflinePlayer - * @param status the status - * @throws IllegalStateException if the server version is unsupported - */ - void setPlayerSilentChestStatus(@NotNull OfflinePlayer offline, boolean status); - - /** - * Forcibly unload a cached Player's data. - * - * @param offline the OfflinePlayer to unload - * @throws IllegalStateException if the server version is unsupported - */ - void unload(@NotNull OfflinePlayer offline); - - Logger getLogger(); + /** + * Check if the server version is supported by OpenInv. + * + * @return true if the server version is supported + */ + boolean isSupportedVersion(); + + /** + * Check the configuration value for whether OpenInv saves player data when unloading players. This is exclusively + * for users who do not allow editing of inventories, only viewing, and wish to prevent any possibility of bugs such + * as lishid#40. If true, OpenInv will not ever save any edits made to players. + * + * @return false unless configured otherwise + */ + boolean disableSaving(); + + /** + * Check the configuration value for whether OpenInv allows offline access. If true, OpenInv will not load or allow + * modification of players while they are not online. This does not prevent other plugins from using existing loaded + * players who have gone offline. + * + * @return false unless configured otherwise + * @since 4.2.0 + */ + boolean disableOfflineAccess(); + + /** + * Check the configuration value for whether OpenInv uses history for opening commands. If false, OpenInv will use + * the previous parameterized search when no parameters are provided. + * + * @return false unless configured otherwise + * @since 4.3.0 + */ + boolean noArgsOpensSelf(); + + /** + * Get the active {@link IAnySilentContainer} implementation. + * + * @return the active implementation for the server version + * @throws IllegalStateException if the server version is unsupported + */ + @NotNull IAnySilentContainer getAnySilentContainer(); + + /** + * Get whether a user has AnyContainer mode enabled. + * + * @param offline the user to obtain the state of + * @return true if AnyContainer mode is enabled + */ + boolean getAnyContainerStatus(@NotNull OfflinePlayer offline); + + /** + * Set whether a user has AnyContainer mode enabled. + * + * @param offline the user to set the state of + * @param status the state of the mode + */ + void setAnyContainerStatus(@NotNull OfflinePlayer offline, boolean status); + + /** + * Get whether a user has SilentContainer mode enabled. + * + * @param offline the user to obtain the state of + * @return true if SilentContainer mode is enabled + */ + boolean getSilentContainerStatus(@NotNull OfflinePlayer offline); + + /** + * Set whether a user has SilentContainer mode enabled. + * + * @param offline the user to set the state of + * @param status the state of the mode + */ + void setSilentContainerStatus(@NotNull OfflinePlayer offline, boolean status); + + /** + * Get an {@link ISpecialEnderChest} for a user. + * + * @param player the {@link Player} owning the inventory + * @param online whether the owner is currently online + * @return the created inventory + * @throws IllegalStateException if the server version is unsupported + * @throws InstantiationException if there was an issue creating the inventory + */ + @NotNull ISpecialEnderChest getSpecialEnderChest( + @NotNull Player player, + boolean online + ) throws InstantiationException; + + /** + * Get an {@link ISpecialPlayerInventory} for a user. + * + * @param player the {@link Player} owning the inventory + * @param online whether the owner is currently online + * @return the created inventory + * @throws IllegalStateException if the server version is unsupported + * @throws InstantiationException if there was an issue creating the inventory + */ + @NotNull ISpecialPlayerInventory getSpecialInventory( + @NotNull Player player, + boolean online + ) throws InstantiationException; + + /** + * @deprecated Use {@link #openInventory(Player, ISpecialInventory, boolean)} + */ + @Deprecated(forRemoval = true, since = "5.2.0") + @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory); + + /** + * Open an {@link ISpecialInventory} for a {@link Player}. + * + * @param player the viewer + * @param inventory the inventory to open + * @param viewOnly whether the inventory should be view-only + * @return the resulting {@link InventoryView} + */ + @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly); + + /** + * Check if a {@link Player} is currently loaded by OpenInv. + * + * @param playerUuid the {@link UUID} of the {@code Player} + * @return whether the {@code Player} is loaded + * @since 4.2.0 + */ + boolean isPlayerLoaded(@NotNull UUID playerUuid); + + /** + * Load a {@link Player} from an {@link OfflinePlayer}. If the user has not played before or the default world for + * the server is not loaded, this will return {@code null}. + * + * @param offline the {@code OfflinePlayer} to load a {@code Player} for + * @return the loaded {@code Player} + * @throws IllegalStateException if the server version is unsupported + */ + @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline); + + /** + * Match an existing {@link OfflinePlayer}. If the name is a {@link UUID#toString() UUID string}, this will only + * return the user if they have actually played on the server before, unlike {@link Bukkit#getOfflinePlayer(UUID)}. + * + *
This method is potentially very heavily blocking. It should not ever be called on the + * main thread, and if it is, a stack trace will be displayed alerting server owners to the + * call. + * + * @param name the string to match + * @return the user with the closest matching name + */ + @Nullable OfflinePlayer matchPlayer(@NotNull String name); + + /** + * Forcibly close inventories of and unload any cached data for a user. + * + * @param offline the {@link OfflinePlayer} to unload + */ + void unload(@NotNull OfflinePlayer offline); + + Logger getLogger(); } diff --git a/api/src/main/java/com/lishid/openinv/event/OpenPlayerSaveEvent.java b/api/src/main/java/com/lishid/openinv/event/OpenPlayerSaveEvent.java new file mode 100644 index 00000000..7f0e16c6 --- /dev/null +++ b/api/src/main/java/com/lishid/openinv/event/OpenPlayerSaveEvent.java @@ -0,0 +1,56 @@ +package com.lishid.openinv.event; + +import com.google.errorprone.annotations.RestrictedApi; +import com.lishid.openinv.internal.ISpecialInventory; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Event fired before OpenInv saves a player's data when closing an {@link ISpecialInventory}. + */ +public class OpenPlayerSaveEvent extends PlayerSaveEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final ISpecialInventory inventory; + + /** + * Construct a new {@code OpenPlayerSaveEvent}. + * + *
The constructor is not considered part of the API, and may be subject to change.
+ * + * @param player the player to be saved + * @param inventory the {@link ISpecialInventory} being closed + */ + @RestrictedApi( + explanation = "Constructor is not considered part of the API and may be subject to change.", + allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java" + ) + @ApiStatus.Internal + OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) { + super(player); + this.inventory = inventory; + } + + /** + * Get the {@link ISpecialInventory} that triggered the save by being closed. + * + * @return the special inventory + */ + public @NotNull ISpecialInventory getInventory() { + return inventory; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + +} diff --git a/api/src/main/java/com/lishid/openinv/event/PlayerSaveEvent.java b/api/src/main/java/com/lishid/openinv/event/PlayerSaveEvent.java new file mode 100644 index 00000000..3fd98039 --- /dev/null +++ b/api/src/main/java/com/lishid/openinv/event/PlayerSaveEvent.java @@ -0,0 +1,67 @@ +package com.lishid.openinv.event; + + +import com.google.errorprone.annotations.RestrictedApi; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Event fired before a {@link Player} loaded via OpenInv is saved. + */ +public class PlayerSaveEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + + private boolean cancelled = false; + + /** + * Construct a new {@code PlayerSaveEvent}. + * + *The constructor is not considered part of the API, and may be subject to change.
+ * + * @param player the player to be saved + */ + @RestrictedApi( + explanation = "Constructor is not considered part of the API and may be subject to change.", + allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java" + ) + @ApiStatus.Internal + PlayerSaveEvent(@NotNull Player player) { + super(player); + } + + /** + * Get whether the event is cancelled. + * + * @return true if the event is cancelled + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Set whether the event is cancelled. + * + * @param cancel whether the event is cancelled + */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + +} diff --git a/api/src/main/java/com/lishid/openinv/event/PlayerToggledEvent.java b/api/src/main/java/com/lishid/openinv/event/PlayerToggledEvent.java new file mode 100644 index 00000000..ed00a5ee --- /dev/null +++ b/api/src/main/java/com/lishid/openinv/event/PlayerToggledEvent.java @@ -0,0 +1,71 @@ +package com.lishid.openinv.event; + +import com.google.errorprone.annotations.RestrictedApi; +import com.lishid.openinv.util.setting.PlayerToggle; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Event fired after OpenInv modifies a toggleable setting for a player. + */ +public class PlayerToggledEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final @NotNull PlayerToggle toggle; + private final @NotNull UUID uuid; + private final boolean enabled; + + @RestrictedApi( + explanation = "Constructor is not considered part of the API and may be subject to change.", + allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java" + ) + @ApiStatus.Internal + PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) { + this.toggle = toggle; + this.uuid = uuid; + this.enabled = enabled; + } + + /** + * Get the {@link PlayerToggle} affected. + * + * @return the toggle + */ + public @NotNull PlayerToggle getToggle() { + return toggle; + } + + /** + * Get the {@link UUID} of the player whose setting was changed. + * + * @return the player ID + */ + public @NotNull UUID getPlayerId() { + return uuid; + } + + /** + * Get whether the toggle is enabled. + * + * @return true if the toggle is enabled + */ + public boolean isEnabled() { + return enabled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + +} diff --git a/api/src/main/java/com/lishid/openinv/internal/IAnySilentContainer.java b/api/src/main/java/com/lishid/openinv/internal/IAnySilentContainer.java index 02cab779..3f83147a 100644 --- a/api/src/main/java/com/lishid/openinv/internal/IAnySilentContainer.java +++ b/api/src/main/java/com/lishid/openinv/internal/IAnySilentContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2020 lishid. All rights reserved. + * Copyright (C) 2011-2023 lishid. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,45 +17,106 @@ package com.lishid.openinv.internal; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.EnderChest; +import org.bukkit.block.data.Directional; +import org.bukkit.entity.Cat; import org.bukkit.entity.Player; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.NotNull; public interface IAnySilentContainer { - /** - * Opens the container at the given coordinates for the Player. If you do not want blocked - * containers to open, be sure to check {@link #isAnyContainerNeeded(Player, Block)} - * first. - * - * @param player the Player opening the container - * @param silent whether the container's noise is to be silenced - * @param block the Block - * @return true if the container can be opened - */ - boolean activateContainer(@NotNull Player player, boolean silent, @NotNull Block block); - - /** - * Closes the Player's currently open container silently, if necessary. - * - * @param player the Player closing a container - */ - void deactivateContainer(@NotNull Player player); - - /** - * Checks if the container at the given coordinates is blocked. - * - * @param player the Player opening the container - * @param block the Block - * @return true if the container is blocked - */ - boolean isAnyContainerNeeded(@NotNull Player player, @NotNull Block block); - - /** - * Checks if the given block is a container which can be unblocked or silenced. - * - * @param block the BlockState - * @return true if the Block is a supported container - */ - boolean isAnySilentContainer(@NotNull Block block); + /** + * Forcibly open the container at the given coordinates for the Player. This will open blocked containers! Be sure + * to check {@link #isAnyContainerNeeded(Block)} first if that is not desirable. + * + * @param player the {@link Player} opening the container + * @param silent whether the container's noise is to be silenced + * @param block the {@link Block} of the container + * @return true if the container can be opened + */ + boolean activateContainer(@NotNull Player player, boolean silent, @NotNull Block block); + + /** + * Perform operations required to close the current container silently. + * + * @param player the {@link Player} closing a container + */ + void deactivateContainer(@NotNull Player player); + + /** + * Check if the container at the given coordinates is blocked. + * + * @param block the {@link Block} of the container + * @return true if the container is blocked + */ + boolean isAnyContainerNeeded(@NotNull Block block); + + /** + * Check if a shulker box block cannot be opened under ordinary circumstances. + * + * @param shulkerBox the shulker box block + * @return whether the container is blocked + */ + default boolean isShulkerBlocked(@NotNull Block shulkerBox) { + Directional directional = (Directional) shulkerBox.getBlockData(); + BlockFace facing = directional.getFacing(); + // Construct a new 1-block bounding box at the origin. + BoundingBox box = new BoundingBox(0, 0, 0, 1, 1, 1); + // Expand the box in the direction the shulker will open. + box.expand(facing, 0.5); + // Move the box away from the origin by a block so only the expansion intersects with a box around the origin. + box.shift(facing.getOppositeFace().getDirection()); + // Check if the relative block's collision shape (which will be at the origin) intersects with the expanded box. + return shulkerBox.getRelative(facing).getCollisionShape().overlaps(box); + } + + /** + * Check if a chest cannot be opened under ordinary circumstances. + * + * @param chest the chest block + * @return whether the container is blocked + */ + default boolean isChestBlocked(@NotNull Block chest) { + org.bukkit.block.Block relative = chest.getRelative(0, 1, 0); + return relative.getType().isOccluding() + || !chest.getWorld().getNearbyEntities(BoundingBox.of(relative), Cat.class::isInstance).isEmpty(); + } + + /** + * Check if the given {@link Block} is a container which can be unblocked or silenced. + * + * @param block the potential container + * @return true if the type is a supported container + */ + boolean isAnySilentContainer(@NotNull Block block); + + /** + * Check if the given {@link BlockState} is a container which can be unblocked or silenced. + * + * @param blockState the potential container + * @return true if the type is a supported container + */ + default boolean isAnySilentContainer(@NotNull BlockState blockState) { + return (blockState instanceof InventoryHolder holder && isAnySilentContainer(holder)) + || blockState instanceof EnderChest; + } + + /** + * Check if the given {@link InventoryHolder} is a container which can be unblocked or silenced. + * + * @param holder the potential container + * @return true if the type is a supported container + */ + default boolean isAnySilentContainer(@NotNull InventoryHolder holder) { + return holder instanceof org.bukkit.block.EnderChest + || holder instanceof org.bukkit.block.Chest + || holder instanceof org.bukkit.block.DoubleChest + || holder instanceof org.bukkit.block.ShulkerBox + || holder instanceof org.bukkit.block.Barrel; + } } diff --git a/api/src/main/java/com/lishid/openinv/internal/IInventoryAccess.java b/api/src/main/java/com/lishid/openinv/internal/IInventoryAccess.java deleted file mode 100644 index 37677047..00000000 --- a/api/src/main/java/com/lishid/openinv/internal/IInventoryAccess.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2011-2020 lishid. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, seeThis casts the field to the correct class. Any issues will result in a {@code null} return value.
+ *
+ * @param fieldType the {@link Class} of {@code Object} stored in the {@code Field}
+ * @param holder the containing {@code Object}
+ * @param Unmodifiable because I said so. Use your own crafting grid. Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories. Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular
- * player inventory slot number.
- *
- * @param view the open inventory view
- * @param rawSlot the raw slot in the view
- * @return the converted slot number
- */
- public int convertToPlayerSlot(InventoryView view, int rawSlot) {
- return this.accessor.getPlayerDataManager().convertToPlayerSlot(view, rawSlot);
- }
-
- @Override
- public boolean disableSaving() {
- return this.getConfig().getBoolean("settings.disable-saving", false);
- }
-
- @NotNull
- @Override
- public IAnySilentContainer getAnySilentContainer() {
- return this.accessor.getAnySilentContainer();
- }
-
- @Override
- public boolean getPlayerAnyChestStatus(@NotNull final OfflinePlayer player) {
- boolean defaultState = false;
-
- if (player.isOnline()) {
- Player onlinePlayer = player.getPlayer();
- if (onlinePlayer != null) {
- defaultState = Permissions.ANY_DEFAULT.hasPermission(onlinePlayer);
- }
- }
-
- return this.getConfig().getBoolean("toggles.any-chest." + this.getPlayerID(player), defaultState);
- }
-
- @Override
- public boolean getPlayerSilentChestStatus(@NotNull final OfflinePlayer offline) {
- boolean defaultState = false;
-
- if (offline.isOnline()) {
- Player onlinePlayer = offline.getPlayer();
- if (onlinePlayer != null) {
- defaultState = Permissions.SILENT_DEFAULT.hasPermission(onlinePlayer);
- }
- }
-
- return this.getConfig().getBoolean("toggles.silent-chest." + this.getPlayerID(offline), defaultState);
- }
-
- @NotNull
- @Override
- public ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online)
- throws InstantiationException {
- String id = this.getPlayerID(player);
- if (this.enderChests.containsKey(id)) {
- return this.enderChests.get(id);
- }
- ISpecialEnderChest inv = this.accessor.newSpecialEnderChest(player, online);
- this.enderChests.put(id, inv);
- this.playerCache.put(id, player);
- return inv;
- }
-
- @NotNull
- @Override
- public ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online)
- throws InstantiationException {
- String id = this.getPlayerID(player);
- if (this.inventories.containsKey(id)) {
- return this.inventories.get(id);
- }
- ISpecialPlayerInventory inv = this.accessor.newSpecialPlayerInventory(player, online);
- this.inventories.put(id, inv);
- this.playerCache.put(id, player);
- return inv;
- }
-
- @Override
- public boolean isSupportedVersion() {
- return this.accessor != null && this.accessor.isSupported();
- }
-
- @Override
- public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
-
- String key = this.getPlayerID(offline);
- if (this.playerCache.containsKey(key)) {
- return this.playerCache.get(key);
- }
-
- Player player = offline.getPlayer();
- if (player != null) {
- this.playerCache.put(key, player);
- return player;
- }
-
- if (!this.isSupportedVersion()) {
- return null;
- }
-
- if (Bukkit.isPrimaryThread()) {
- return this.accessor.getPlayerDataManager().loadPlayer(offline);
- }
-
- Future Implementation note: a return value of {@code null} will cause the command to cease
+ * execution with no feedback. Appropriate feedback should be sent in the implementation. Note that this method is specifically for converting an ISpecialPlayerInventory slot number into a regular
- * player inventory slot number.
- *
- * @param view the open inventory view
- * @param rawSlot the raw slot in the view
- * @return the converted slot number
- */
- int convertToPlayerSlot(InventoryView view, int rawSlot);
-
-}
diff --git a/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java b/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java
deleted file mode 100644
index 8fc6f09d..00000000
--- a/plugin/src/main/java/com/lishid/openinv/internal/OpenInventoryView.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011-2021 lishid. All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see