mirror of
https://github.com/Jikoo/OpenInv.git
synced 2026-11-04 08:42:05 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e26c33311 | ||
|
|
34f5319f48 | ||
|
|
642e287507 | ||
|
|
4504ba2b99 | ||
|
|
9691d16506 | ||
|
|
1897790db5 | ||
|
|
cad76c6559 | ||
|
|
119ff48461 | ||
|
|
b589f79e57 | ||
|
|
f742cf5b77 | ||
|
|
19d030dd4d | ||
|
|
17dec41fea | ||
|
|
b5599450f5 | ||
|
|
b2c13b40e2 | ||
|
|
03c0fea7ab | ||
|
|
9ca5b2d826 | ||
|
|
7a9bb11133 | ||
|
|
8647f8e156 | ||
|
|
2a07673b1a | ||
|
|
8418cb14fe | ||
|
|
6647334072 | ||
|
|
e479a5eaa7 | ||
|
|
1c40091cfa |
No files matched your search
+491
@@ -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
|
||||
@@ -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
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create-release
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
+13
-2
@@ -1,7 +1,10 @@
|
||||
## About
|
||||
OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/openinv) which allows users to open and edit anyone's inventory or ender chest - online or not!
|
||||
|
||||
OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/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! Don't grant edit permission.
|
||||
- Cross-world support! Allow access only from the same world.
|
||||
@@ -16,15 +19,19 @@ OpenInv is a [Bukkit plugin](https://dev.bukkit.org/projects/openinv) which allo
|
||||
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
|
||||
|
||||
## Commands
|
||||
|
||||
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Commands).
|
||||
|
||||
## Permissions
|
||||
|
||||
See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Permissions)
|
||||
|
||||
## For Developers
|
||||
|
||||
### As a Dependency
|
||||
|
||||
The OpenInv API is available via [JitPack](https://jitpack.io/).
|
||||
|
||||
```xml
|
||||
<repositories>
|
||||
<repository>
|
||||
@@ -33,6 +40,7 @@ The OpenInv API is available via [JitPack](https://jitpack.io/).
|
||||
</repository>
|
||||
</repositories>
|
||||
```
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -42,9 +50,12 @@ The OpenInv API is available via [JitPack](https://jitpack.io/).
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
Note that since JitPack only builds the API now, the "full" OpenInv jar on JitPack is actually the openinvapi artifact. This is a change from previous dependency declaration that I hope to revert.
|
||||
|
||||
Note that since JitPack only builds the API now, the "full" OpenInv jar on JitPack is actually the openinvapi artifact.
|
||||
This is a change from previous dependency declaration that I hope to revert.
|
||||
|
||||
### Compilation
|
||||
|
||||
Execute the gradle wrapper:
|
||||
`./gradlew build`
|
||||
|
||||
+27
-18
@@ -64,13 +64,16 @@ public class TogglePersist extends JavaPlugin implements Listener {
|
||||
}
|
||||
|
||||
private void set(UUID playerId, String toggleName) {
|
||||
enabledToggles.compute(playerId, (uuid, toggles) -> {
|
||||
if (toggles == null) {
|
||||
toggles = new HashSet<>();
|
||||
}
|
||||
toggles.add(toggleName);
|
||||
return toggles;
|
||||
});
|
||||
enabledToggles.compute(
|
||||
playerId,
|
||||
(uuid, toggles) -> {
|
||||
if (toggles == null) {
|
||||
toggles = new HashSet<>();
|
||||
}
|
||||
toggles.add(toggleName);
|
||||
return toggles;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,13 +100,16 @@ public class TogglePersist extends JavaPlugin implements Listener {
|
||||
String idString = playerToggles.getKey().toString();
|
||||
for (String toggleName : playerToggles.getValue()) {
|
||||
// Add player ID to listing for each enabled toggle.
|
||||
converted.compute(toggleName, (name, ids) -> {
|
||||
if (ids == null) {
|
||||
ids = new ArrayList<>();
|
||||
}
|
||||
ids.add(idString);
|
||||
return ids;
|
||||
});
|
||||
converted.compute(
|
||||
toggleName,
|
||||
(name, ids) -> {
|
||||
if (ids == null) {
|
||||
ids = new ArrayList<>();
|
||||
}
|
||||
ids.add(idString);
|
||||
return ids;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
@@ -133,10 +139,13 @@ public class TogglePersist extends JavaPlugin implements Listener {
|
||||
if (event.isEnabled()) {
|
||||
set(event.getPlayerId(), event.getToggle().getName());
|
||||
} else {
|
||||
enabledToggles.computeIfPresent(event.getPlayerId(), (uuid, toggles) -> {
|
||||
toggles.remove(event.getToggle().getName());
|
||||
return toggles.isEmpty() ? null : toggles;
|
||||
});
|
||||
enabledToggles.computeIfPresent(
|
||||
event.getPlayerId(),
|
||||
(uuid, toggles) -> {
|
||||
toggles.remove(event.getToggle().getName());
|
||||
return toggles.isEmpty() ? null : toggles;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,151 +35,157 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public interface IOpenInv {
|
||||
|
||||
/**
|
||||
* Check if the server version is supported by OpenInv.
|
||||
*
|
||||
* @return true if the server version is supported
|
||||
*/
|
||||
boolean isSupportedVersion();
|
||||
/**
|
||||
* 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 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 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();
|
||||
/**
|
||||
* 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 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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 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;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Open an {@link ISpecialInventory} for a {@link Player}.
|
||||
*
|
||||
* @param player the viewer
|
||||
* @param inventory the inventory to open
|
||||
* @return the resulting {@link InventoryView}
|
||||
*/
|
||||
@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
|
||||
* @return the resulting {@link InventoryView}
|
||||
*/
|
||||
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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)}.
|
||||
*
|
||||
* <p>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);
|
||||
/**
|
||||
* 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)}.
|
||||
*
|
||||
* <p>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);
|
||||
/**
|
||||
* 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();
|
||||
Logger getLogger();
|
||||
|
||||
}
|
||||
@@ -26,8 +26,8 @@ public class OpenPlayerSaveEvent extends PlayerSaveEvent {
|
||||
*/
|
||||
@RestrictedApi(
|
||||
explanation = "Constructor is not considered part of the API and may be subject to change.",
|
||||
link = "",
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
|
||||
)
|
||||
@ApiStatus.Internal
|
||||
OpenPlayerSaveEvent(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
super(player);
|
||||
|
||||
@@ -27,8 +27,8 @@ public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
|
||||
*/
|
||||
@RestrictedApi(
|
||||
explanation = "Constructor is not considered part of the API and may be subject to change.",
|
||||
link = "",
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java")
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/(OpenPlayerSaveEvent|OpenEvents).java"
|
||||
)
|
||||
@ApiStatus.Internal
|
||||
PlayerSaveEvent(@NotNull Player player) {
|
||||
super(player);
|
||||
|
||||
@@ -22,8 +22,8 @@ public class PlayerToggledEvent extends Event {
|
||||
|
||||
@RestrictedApi(
|
||||
explanation = "Constructor is not considered part of the API and may be subject to change.",
|
||||
link = "",
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java")
|
||||
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
|
||||
)
|
||||
@ApiStatus.Internal
|
||||
PlayerToggledEvent(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean enabled) {
|
||||
this.toggle = toggle;
|
||||
|
||||
@@ -29,94 +29,94 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface IAnySilentContainer {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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 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 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 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 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 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,46 +27,49 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public interface ISpecialInventory {
|
||||
|
||||
/**
|
||||
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
|
||||
*
|
||||
* @return the Bukkit inventory
|
||||
*/
|
||||
@NotNull Inventory getBukkitInventory();
|
||||
/**
|
||||
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
|
||||
*
|
||||
* @return the Bukkit inventory
|
||||
*/
|
||||
@NotNull Inventory getBukkitInventory();
|
||||
|
||||
/**
|
||||
* Get the {@link InventoryType} corresponding to this {@code ISpecialInventory}.
|
||||
*
|
||||
* @return the type of Bukkit inventory
|
||||
*/
|
||||
@NotNull InventoryType getBukkitType();
|
||||
/**
|
||||
* Get the {@link InventoryType} corresponding to this {@code ISpecialInventory}.
|
||||
*
|
||||
* @return the type of Bukkit inventory
|
||||
*/
|
||||
@NotNull InventoryType getBukkitType();
|
||||
|
||||
/**
|
||||
* Set the owning {@link Player} instance to a newly-joined user.
|
||||
*
|
||||
* @param player the user coming online
|
||||
*/
|
||||
void setPlayerOnline(@NotNull Player player);
|
||||
/**
|
||||
* Set the owning {@link Player} instance to a newly-joined user.
|
||||
*
|
||||
* @param player the user coming online
|
||||
*/
|
||||
void setPlayerOnline(@NotNull Player player);
|
||||
|
||||
/**
|
||||
* Mark the owner of the inventory offline.
|
||||
*/
|
||||
void setPlayerOffline();
|
||||
/**
|
||||
* Mark the owner of the inventory offline.
|
||||
*
|
||||
* @deprecated No longer used by implementations.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.1.11")
|
||||
default void setPlayerOffline() {}
|
||||
|
||||
/**
|
||||
* Get whether the inventory is being viewed by any users.
|
||||
*
|
||||
* @return true if the inventory is being viewed
|
||||
*/
|
||||
default boolean isInUse() {
|
||||
return !getBukkitInventory().getViewers().isEmpty();
|
||||
}
|
||||
/**
|
||||
* Get whether the inventory is being viewed by any users.
|
||||
*
|
||||
* @return true if the inventory is being viewed
|
||||
*/
|
||||
default boolean isInUse() {
|
||||
return !getBukkitInventory().getViewers().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Player} who owns the inventory.
|
||||
*
|
||||
* @return the {@link HumanEntity} who owns the inventory
|
||||
*/
|
||||
@NotNull HumanEntity getPlayer();
|
||||
/**
|
||||
* Get the {@link Player} who owns the inventory.
|
||||
*
|
||||
* @return the {@link HumanEntity} who owns the inventory
|
||||
*/
|
||||
@NotNull HumanEntity getPlayer();
|
||||
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public interface ISpecialPlayerInventory extends ISpecialInventory {
|
||||
|
||||
@Override
|
||||
default @NotNull InventoryType getBukkitType() {
|
||||
return InventoryType.PLAYER;
|
||||
}
|
||||
@Override
|
||||
default @NotNull InventoryType getBukkitType() {
|
||||
return InventoryType.PLAYER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,76 +29,78 @@ import java.util.function.BiFunction;
|
||||
|
||||
public final class InventoryAccess {
|
||||
|
||||
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
|
||||
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider;
|
||||
|
||||
public static boolean isUsable() {
|
||||
return provider != null;
|
||||
}
|
||||
public static boolean isUsable() {
|
||||
return provider != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an {@link Inventory} is an {@link ISpecialPlayerInventory} implementation.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return true if backed by the correct implementation
|
||||
*/
|
||||
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
|
||||
return getPlayerInventory(inventory) != null;
|
||||
}
|
||||
/**
|
||||
* Check if an {@link Inventory} is an {@link ISpecialPlayerInventory} implementation.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return true if backed by the correct implementation
|
||||
*/
|
||||
public static boolean isPlayerInventory(@NotNull Inventory inventory) {
|
||||
return getPlayerInventory(inventory) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ISpecialPlayerInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is
|
||||
* not backed by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
|
||||
}
|
||||
/**
|
||||
* Get the {@link ISpecialPlayerInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is
|
||||
* not backed by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialPlayerInventory getPlayerInventory(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : (ISpecialPlayerInventory) provider.apply(inventory, ISpecialPlayerInventory.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an {@link Inventory} is an {@link ISpecialEnderChest} implementation.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return true if backed by the correct implementation
|
||||
*/
|
||||
public static boolean isEnderChest(@NotNull Inventory inventory) {
|
||||
return getEnderChest(inventory) != null;
|
||||
}
|
||||
/**
|
||||
* Check if an {@link Inventory} is an {@link ISpecialEnderChest} implementation.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return true if backed by the correct implementation
|
||||
*/
|
||||
public static boolean isEnderChest(@NotNull Inventory inventory) {
|
||||
return getEnderChest(inventory) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ISpecialEnderChest} backing an {@link Inventory}. Returns {@code null} if the inventory is
|
||||
* not backed by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
|
||||
}
|
||||
/**
|
||||
* Get the {@link ISpecialEnderChest} backing an {@link Inventory}. Returns {@code null} if the inventory is
|
||||
* not backed by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialEnderChest getEnderChest(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : (ISpecialEnderChest) provider.apply(inventory, ISpecialEnderChest.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link ISpecialInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is not backed
|
||||
* by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialInventory getInventory(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : provider.apply(inventory, ISpecialInventory.class);
|
||||
}
|
||||
/**
|
||||
* Get a {@link ISpecialInventory} backing an {@link Inventory}. Returns {@code null} if the inventory is not backed
|
||||
* by the correct class.
|
||||
*
|
||||
* @param inventory the Bukkit inventory
|
||||
* @return the backing implementation if available
|
||||
*/
|
||||
public static @Nullable ISpecialInventory getInventory(@NotNull Inventory inventory) {
|
||||
return provider == null ? null : provider.apply(inventory, ISpecialInventory.class);
|
||||
}
|
||||
|
||||
@RestrictedApi(
|
||||
explanation = "Not part of the API.",
|
||||
link = "",
|
||||
allowedOnPath = ".*/com/lishid/openinv/util/InternalAccessor.java")
|
||||
@ApiStatus.Internal
|
||||
static void setProvider(@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider) {
|
||||
InventoryAccess.provider = provider;
|
||||
}
|
||||
@RestrictedApi(
|
||||
explanation = "Not part of the API.",
|
||||
allowedOnPath = ".*/com/lishid/openinv/util/InternalAccessor.java"
|
||||
)
|
||||
@ApiStatus.Internal
|
||||
static void setProvider(
|
||||
@Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, ISpecialInventory> provider
|
||||
) {
|
||||
InventoryAccess.provider = provider;
|
||||
}
|
||||
|
||||
private InventoryAccess() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
private InventoryAccess() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ plugins {
|
||||
`java-library`
|
||||
alias(libs.plugins.paperweight) apply false
|
||||
alias(libs.plugins.shadow) apply false
|
||||
id(libs.plugins.errorprone.gradle.get().pluginId) apply false
|
||||
}
|
||||
|
||||
// Set by Spigot module, used by Paper module to convert to Spigot's version of Mojang mappings.
|
||||
|
||||
@@ -10,4 +10,5 @@ repositories {
|
||||
dependencies {
|
||||
val libs = project.extensions.getByType(VersionCatalogsExtension::class.java).named("libs")
|
||||
implementation(libs.findLibrary("specialsource").orElseThrow())
|
||||
implementation(libs.findLibrary("errorprone-gradle").orElseThrow())
|
||||
}
|
||||
@@ -10,12 +10,12 @@ import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BuildToolsValueSource: ValueSource<File, BuildToolsValueSource.Parameters> {
|
||||
abstract class BuildToolsValueSource : ValueSource<File, BuildToolsValueSource.Parameters> {
|
||||
|
||||
@get:Inject
|
||||
abstract val exec: ExecOperations
|
||||
|
||||
interface Parameters: ValueSourceParameters {
|
||||
interface Parameters : ValueSourceParameters {
|
||||
val mavenLocal: Property<File>
|
||||
val workingDir: DirectoryProperty
|
||||
|
||||
@@ -55,7 +55,8 @@ abstract class BuildToolsValueSource: ValueSource<File, BuildToolsValueSource.Pa
|
||||
|
||||
if (!installLocation.exists()) {
|
||||
throw IllegalStateException(
|
||||
"Failed to install Spigot $version from $revision. Does the revision point to a different version?")
|
||||
"Failed to install Spigot $version from $revision. Does the revision point to a different version?"
|
||||
)
|
||||
}
|
||||
return installLocation
|
||||
}
|
||||
@@ -72,7 +73,8 @@ abstract class BuildToolsValueSource: ValueSource<File, BuildToolsValueSource.Pa
|
||||
|
||||
workingDir.mkdirs()
|
||||
|
||||
val buildToolsUrl = "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"
|
||||
val buildToolsUrl =
|
||||
"https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"
|
||||
println("Downloading $buildToolsUrl")
|
||||
val stream = URI.create(buildToolsUrl).toURL().openStream()
|
||||
Files.copy(stream, buildTools.toPath())
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.github.jikoo.openinv
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.jvm.toolchain.JavaToolchainSpec
|
||||
|
||||
abstract class SpigotDependencyExtension (
|
||||
abstract class SpigotDependencyExtension(
|
||||
objects: ObjectFactory
|
||||
) {
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.gradle.kotlin.dsl.named
|
||||
import org.gradle.kotlin.dsl.register
|
||||
import java.nio.file.Paths
|
||||
|
||||
class SpigotReobf: Plugin<Project> {
|
||||
class SpigotReobf : Plugin<Project> {
|
||||
|
||||
companion object {
|
||||
const val ARTIFACT_CONFIG = "reobf"
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
|
||||
abstract class SpigotReobfTask: org.gradle.api.tasks.bundling.Jar() {
|
||||
abstract class SpigotReobfTask : org.gradle.api.tasks.bundling.Jar() {
|
||||
|
||||
@get:Input
|
||||
val spigotVersion: Property<String> = objectFactory.property(String::class.java)
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.gradle.kotlin.dsl.create
|
||||
import java.nio.file.Paths
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SpigotSetup: Plugin<Project> {
|
||||
abstract class SpigotSetup : Plugin<Project> {
|
||||
|
||||
@get:Inject
|
||||
abstract val javaToolchainService: JavaToolchainService
|
||||
@@ -51,7 +51,8 @@ abstract class SpigotSetup: Plugin<Project> {
|
||||
spigotExt.version.get(),
|
||||
spigotExt.configuration.orNull,
|
||||
spigotExt.classifier.orNull,
|
||||
spigotExt.ext.orNull)
|
||||
spigotExt.ext.orNull
|
||||
)
|
||||
target.dependencies.add("compileOnly", dependency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
import org.gradle.kotlin.dsl.maven
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("net.ltgt.errorprone")
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -21,6 +17,7 @@ dependencies {
|
||||
val libs = versionCatalogs.named("libs")
|
||||
compileOnly(libs.findLibrary("annotations").orElseThrow())
|
||||
compileOnly(libs.findLibrary("spigotapi").orElseThrow())
|
||||
errorprone(libs.findLibrary("errorprone-core").orElseThrow())
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -4,4 +4,5 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
compileOnly(libs.slf4j.api)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.lishid.openinv.internal;
|
||||
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public enum InventoryViewTitle {
|
||||
|
||||
PLAYER_INVENTORY("container.player", "'s Inventory"),
|
||||
ENDER_CHEST("container.enderchest", "'s Ender Chest");
|
||||
|
||||
private final String localizationKey;
|
||||
private final String defaultSuffix;
|
||||
|
||||
InventoryViewTitle(String localizationKey, String defaultSuffix) {
|
||||
this.localizationKey = localizationKey;
|
||||
this.defaultSuffix = defaultSuffix;
|
||||
}
|
||||
|
||||
public @NotNull String getTitle(
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull Player viewer,
|
||||
@NotNull ISpecialInventory inventory) {
|
||||
HumanEntity owner = inventory.getPlayer();
|
||||
|
||||
String localTitle = lang.getLocalizedMessage(
|
||||
viewer,
|
||||
localizationKey,
|
||||
new Replacement("%player%", owner.getName()));
|
||||
return Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + defaultSuffix);
|
||||
}
|
||||
|
||||
public static @Nullable InventoryViewTitle of(@NotNull ISpecialInventory inventory) {
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
return PLAYER_INVENTORY;
|
||||
} else if (inventory instanceof ISpecialEnderChest) {
|
||||
return ENDER_CHEST;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,31 +24,31 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface PlayerManager {
|
||||
|
||||
/**
|
||||
* Loads a Player for an OfflinePlayer.
|
||||
* </p>
|
||||
* This method is potentially blocking, and should not be called on the main thread.
|
||||
*
|
||||
* @param offline the OfflinePlayer
|
||||
* @return the Player loaded
|
||||
*/
|
||||
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
|
||||
/**
|
||||
* Loads a Player for an OfflinePlayer.
|
||||
* </p>
|
||||
* This method is potentially blocking, and should not be called on the main thread.
|
||||
*
|
||||
* @param offline the OfflinePlayer
|
||||
* @return the Player loaded
|
||||
*/
|
||||
@Nullable Player loadPlayer(@NotNull OfflinePlayer offline);
|
||||
|
||||
/**
|
||||
* Creates a new Player from an existing one that will function slightly better offline.
|
||||
*
|
||||
* @return the Player
|
||||
*/
|
||||
@NotNull Player inject(@NotNull Player player);
|
||||
/**
|
||||
* Creates a new Player from an existing one that will function slightly better offline.
|
||||
*
|
||||
* @return the Player
|
||||
*/
|
||||
@NotNull Player inject(@NotNull Player player);
|
||||
|
||||
/**
|
||||
* Opens an ISpecialInventory for a Player.
|
||||
*
|
||||
* @param player the Player opening the ISpecialInventory
|
||||
* @param inventory the Inventory
|
||||
*`
|
||||
* @return the InventoryView opened
|
||||
*/
|
||||
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
|
||||
/**
|
||||
* Opens an ISpecialInventory for a Player.
|
||||
*
|
||||
* @param player the Player opening the ISpecialInventory
|
||||
* @param inventory the Inventory
|
||||
*
|
||||
* @return the InventoryView opened
|
||||
*/
|
||||
@Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.helpers.LegacyAbstractLogger;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
import org.slf4j.helpers.NormalizedParameters;
|
||||
import org.slf4j.spi.LocationAwareLogger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* An adapter for wrapping a {@link java.util.logging.Logger} as a {@link org.slf4j.Logger}.
|
||||
* <br>Largely based on {@code JDK14LoggerAdapter}, which is not present at runtime.
|
||||
*/
|
||||
public class JulLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger {
|
||||
|
||||
private final Logger wrapped;
|
||||
|
||||
public JulLoggerAdapter(Logger wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
this.name = wrapped.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFullyQualifiedCallerName() {
|
||||
return JulLoggerAdapter.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleNormalizedLoggingCall(
|
||||
org.slf4j.event.Level slf4jLevel,
|
||||
Marker marker,
|
||||
String msg,
|
||||
Object[] args,
|
||||
Throwable thrown
|
||||
) {
|
||||
Level level = fromSlf4jLevel(slf4jLevel);
|
||||
|
||||
if (wrapped.isLoggable(level)) {
|
||||
normalizedLog(getFullyQualifiedCallerName(), level, msg, args, thrown);
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizedLog(String fqcn, Level level, String msg, Object[] args, Throwable thrown) {
|
||||
String formatted = MessageFormatter.basicArrayFormat(msg, args);
|
||||
LogRecord logRecord = new LogRecord(level, formatted);
|
||||
logRecord.setLoggerName(getName());
|
||||
logRecord.setThrown(thrown);
|
||||
|
||||
addSource(fqcn, logRecord);
|
||||
|
||||
wrapped.log(logRecord);
|
||||
}
|
||||
|
||||
private void addSource(String fqcn, LogRecord logRecord) {
|
||||
// TODO stackwalker?
|
||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||
int maxElements = 12;
|
||||
int lastIgnored = maxElements;
|
||||
// Start from 2; 0 is above and 1 is caller of internal method.
|
||||
for (int i = 2; i < maxElements; ++i) {
|
||||
if (isIgnored(trace[i].getClassName(), fqcn)) {
|
||||
lastIgnored = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastIgnored < maxElements - 1) {
|
||||
StackTraceElement caller = trace[lastIgnored + 1];
|
||||
logRecord.setSourceClassName(caller.getClassName());
|
||||
logRecord.setSourceMethodName(caller.getMethodName());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIgnored(String className, String fqcn) {
|
||||
if (className.equals(fqcn)) {
|
||||
return true;
|
||||
}
|
||||
// Ignore slf4j classes - they shouldn't be the source.
|
||||
if (className.startsWith("org.slf4j.")) {
|
||||
return true;
|
||||
}
|
||||
return className.equals(getFullyQualifiedCallerName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(Marker marker, String callerFqn, int levelInt, String msg, Object[] args, Throwable thrown) {
|
||||
Level level = fromSlf4jLevel(org.slf4j.event.Level.intToLevel(levelInt));
|
||||
|
||||
if (!wrapped.isLoggable(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizedParameters params = NormalizedParameters.normalize(msg, args, thrown);
|
||||
normalizedLog(callerFqn, level, params.getMessage(), params.getArguments(), params.getThrowable());
|
||||
}
|
||||
|
||||
private Level fromSlf4jLevel(org.slf4j.event.Level level) {
|
||||
return switch (level) {
|
||||
case TRACE -> Level.FINEST;
|
||||
case DEBUG -> Level.FINE;
|
||||
case INFO -> Level.INFO;
|
||||
case WARN -> Level.WARNING;
|
||||
case ERROR -> Level.SEVERE;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
return wrapped.isLoggable(Level.FINEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return wrapped.isLoggable(Level.FINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled() {
|
||||
return wrapped.isLoggable(Level.INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled() {
|
||||
return wrapped.isLoggable(Level.WARNING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled() {
|
||||
return wrapped.isLoggable(Level.SEVERE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,40 +21,43 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public enum Permissions {
|
||||
|
||||
INVENTORY_OPEN_SELF("inventory.open.self"),
|
||||
INVENTORY_OPEN_OTHER("inventory.open.other"),
|
||||
INVENTORY_EDIT_SELF("inventory.edit.self"),
|
||||
INVENTORY_EDIT_OTHER("inventory.edit.other"),
|
||||
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
|
||||
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
|
||||
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
|
||||
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
|
||||
INVENTORY_SLOT_DROP("inventory.slot.drop"),
|
||||
INVENTORY_OPEN_SELF("inventory.open.self"),
|
||||
INVENTORY_OPEN_OTHER("inventory.open.other"),
|
||||
INVENTORY_EDIT_SELF("inventory.edit.self"),
|
||||
INVENTORY_EDIT_OTHER("inventory.edit.other"),
|
||||
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
|
||||
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
|
||||
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
|
||||
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
|
||||
INVENTORY_SLOT_DROP("inventory.slot.drop"),
|
||||
|
||||
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
|
||||
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
|
||||
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
|
||||
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
|
||||
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
|
||||
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
|
||||
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
|
||||
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
|
||||
|
||||
ACCESS_CROSSWORLD("access.crossworld"),
|
||||
ACCESS_OFFLINE("access.offline"),
|
||||
ACCESS_ONLINE("access.online"),
|
||||
CLEAR_SELF("clear.self"),
|
||||
CLEAR_OTHER("clear.other"),
|
||||
|
||||
SPECTATE_CLICK("spectate.click"),
|
||||
ACCESS_CROSSWORLD("access.crossworld"),
|
||||
ACCESS_OFFLINE("access.offline"),
|
||||
ACCESS_ONLINE("access.online"),
|
||||
|
||||
CONTAINER_ANY("container.any"),
|
||||
CONTAINER_SILENT("container.silent"),
|
||||
SEARCH_INVENTORY("search.inventory"),
|
||||
SEARCH_CONTAINER("search.container");
|
||||
SPECTATE_CLICK("spectate.click"),
|
||||
|
||||
private final String permission;
|
||||
CONTAINER_ANY("container.any"),
|
||||
CONTAINER_SILENT("container.silent"),
|
||||
SEARCH_INVENTORY("search.inventory"),
|
||||
SEARCH_CONTAINER("search.container");
|
||||
|
||||
Permissions(String permission) {
|
||||
this.permission = "openinv." + permission;
|
||||
}
|
||||
private final String permission;
|
||||
|
||||
public boolean hasPermission(@NotNull Permissible permissible) {
|
||||
return permissible.hasPermission(permission);
|
||||
}
|
||||
Permissions(String permission) {
|
||||
this.permission = "openinv." + permission;
|
||||
}
|
||||
|
||||
public boolean hasPermission(@NotNull Permissible permissible) {
|
||||
return permissible.hasPermission(permission);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,54 +25,54 @@ import java.lang.reflect.Field;
|
||||
*/
|
||||
public final class ReflectionHelper {
|
||||
|
||||
/**
|
||||
* Grab an {@link Object} stored in a {@link Field} of another {@code Object}.
|
||||
*
|
||||
* <p>This 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 <T> the type of stored {@code Object}
|
||||
* @return the first matching {@code Object} or {@code null} if none match
|
||||
*/
|
||||
public static <T> @Nullable T grabObjectByType(final Object holder, final Class<T> fieldType) {
|
||||
Field field = grabFieldByType(holder.getClass(), fieldType);
|
||||
/**
|
||||
* Grab an {@link Object} stored in a {@link Field} of another {@code Object}.
|
||||
*
|
||||
* <p>This 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 <T> the type of stored {@code Object}
|
||||
* @return the first matching {@code Object} or {@code null} if none match
|
||||
*/
|
||||
public static <T> @Nullable T grabObjectByType(final Object holder, final Class<T> fieldType) {
|
||||
Field field = grabFieldByType(holder.getClass(), fieldType);
|
||||
|
||||
if (field != null) {
|
||||
try {
|
||||
return fieldType.cast(field.get(holder));
|
||||
} catch (IllegalAccessException ignored) {
|
||||
// Ignore issues obtaining field
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if (field != null) {
|
||||
try {
|
||||
return fieldType.cast(field.get(holder));
|
||||
} catch (IllegalAccessException ignored) {
|
||||
// Ignore issues obtaining field
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab a {@link Field} of an {@link Object}
|
||||
*
|
||||
* @param fieldType the {@link Class} of the object
|
||||
* @param holderType the containing {@code Class}
|
||||
* @return the first matching object or {@code null} if none match
|
||||
*/
|
||||
public static @Nullable Field grabFieldByType(Class<?> holderType, Class<?> fieldType) {
|
||||
for (Field field : holderType.getDeclaredFields()) {
|
||||
if (fieldType.isAssignableFrom(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (holderType.getSuperclass() != null) {
|
||||
return grabFieldByType(fieldType, holderType.getSuperclass());
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* Grab a {@link Field} of an {@link Object}
|
||||
*
|
||||
* @param fieldType the {@link Class} of the object
|
||||
* @param holderType the containing {@code Class}
|
||||
* @return the first matching object or {@code null} if none match
|
||||
*/
|
||||
public static @Nullable Field grabFieldByType(Class<?> holderType, Class<?> fieldType) {
|
||||
for (Field field : holderType.getDeclaredFields()) {
|
||||
if (fieldType.isAssignableFrom(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
private ReflectionHelper() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
if (holderType.getSuperclass() != null) {
|
||||
return grabFieldByType(fieldType, holderType.getSuperclass());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ReflectionHelper() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,271 +49,278 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class LanguageManager {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final File folder;
|
||||
private final String defaultLocale;
|
||||
private final Map<String, YamlConfiguration> locales;
|
||||
private final Plugin plugin;
|
||||
private final File folder;
|
||||
private final String defaultLocale;
|
||||
private final Map<String, YamlConfiguration> locales;
|
||||
|
||||
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
|
||||
this.plugin = plugin;
|
||||
this.defaultLocale = defaultLocale;
|
||||
this.locales = new HashMap<>();
|
||||
this.folder = new File(plugin.getDataFolder(), "locale");
|
||||
public LanguageManager(@NotNull Plugin plugin, @NotNull String defaultLocale) {
|
||||
this.plugin = plugin;
|
||||
this.defaultLocale = defaultLocale;
|
||||
this.locales = new HashMap<>();
|
||||
this.folder = new File(plugin.getDataFolder(), "locale");
|
||||
|
||||
if (!folder.exists() && !folder.mkdirs()) {
|
||||
plugin.getLogger().warning(() -> "Unable to create " + folder.getPath() + "! Languages may not be editable.");
|
||||
}
|
||||
|
||||
reload();
|
||||
if (!folder.exists() && !folder.mkdirs()) {
|
||||
plugin.getLogger().warning(() -> "Unable to create " + folder.getPath() + "! Languages may not be editable.");
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.locales.clear();
|
||||
getOrLoadLocale(defaultLocale);
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.locales.clear();
|
||||
getOrLoadLocale(defaultLocale);
|
||||
}
|
||||
|
||||
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
|
||||
YamlConfiguration loaded = locales.get(locale);
|
||||
if (loaded != null) {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
|
||||
YamlConfiguration loaded = locales.get(locale);
|
||||
if (loaded != null) {
|
||||
return loaded;
|
||||
}
|
||||
LangLocation lang = bestMatch(locale, null);
|
||||
|
||||
LangLocation lang = bestMatch(locale, null);
|
||||
|
||||
// If a parent was a better match, check if it is already loaded.
|
||||
if (!locale.equals(lang.locale)) {
|
||||
loaded = locales.get(lang.locale);
|
||||
if (loaded != null) {
|
||||
locales.put(locale, loaded);
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// Load locale config from disk and bundled locale defaults.
|
||||
YamlConfiguration localeConfig = loadLocale(lang);
|
||||
|
||||
// If the locale is not the default locale, also handle any missing translations from the default locale.
|
||||
if (!locale.equals(defaultLocale)) {
|
||||
addTranslationFallthrough(lang, localeConfig);
|
||||
|
||||
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
|
||||
&& localeConfig.isConfigurationSection("guess")) {
|
||||
// Warn that guess section exists. This should run once per language per server restart
|
||||
// when accessed by a user to hint to server owners that they can make UX improvements.
|
||||
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + lang.locale + ".yml! Check the guess section!");
|
||||
}
|
||||
}
|
||||
|
||||
locales.put(locale, localeConfig);
|
||||
locales.put(lang.locale, localeConfig);
|
||||
|
||||
return localeConfig;
|
||||
// If a parent was a better match, check if it is already loaded.
|
||||
if (!locale.equals(lang.locale)) {
|
||||
loaded = locales.get(lang.locale);
|
||||
if (loaded != null) {
|
||||
locales.put(locale, loaded);
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
|
||||
File file = new File(folder, locale + ".yml");
|
||||
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
|
||||
// Load locale config from disk and bundled locale defaults.
|
||||
YamlConfiguration localeConfig = loadLocale(lang);
|
||||
|
||||
if (file.exists() || bundled != null) {
|
||||
return new LangLocation(locale, file, bundled);
|
||||
}
|
||||
// If the locale is not the default locale, also handle any missing translations from the default locale.
|
||||
if (!locale.equals(defaultLocale)) {
|
||||
addTranslationFallthrough(lang, localeConfig);
|
||||
|
||||
if (initial == null) {
|
||||
initial = new LangLocation(locale, file, null);
|
||||
}
|
||||
|
||||
int lastSeparator = locale.lastIndexOf('_');
|
||||
|
||||
// Must be at least some content before separator.
|
||||
if (lastSeparator < 1) {
|
||||
return initial;
|
||||
}
|
||||
|
||||
return bestMatch(locale.substring(0, lastSeparator), initial);
|
||||
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
|
||||
&& localeConfig.isConfigurationSection("guess")) {
|
||||
// Warn that guess section exists. This should run once per language per server restart
|
||||
// when accessed by a user to hint to server owners that they can make UX improvements.
|
||||
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + lang.locale + ".yml! Check the guess section!");
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
|
||||
YamlConfiguration localeConfigDefaults;
|
||||
if (lang.bundled == null) {
|
||||
localeConfigDefaults = new YamlConfiguration();
|
||||
} else {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(lang.bundled, StandardCharsets.UTF_8))) {
|
||||
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + lang.locale + ".yml");
|
||||
localeConfigDefaults = new YamlConfiguration();
|
||||
}
|
||||
}
|
||||
locales.put(locale, localeConfig);
|
||||
locales.put(lang.locale, localeConfig);
|
||||
|
||||
if (!lang.file.exists()) {
|
||||
// If the file does not exist on disk, save bundled defaults.
|
||||
try {
|
||||
localeConfigDefaults.save(lang.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
|
||||
}
|
||||
// Return loaded bundled locale.
|
||||
return localeConfigDefaults;
|
||||
}
|
||||
return localeConfig;
|
||||
}
|
||||
|
||||
// If the file does exist on disk, load it.
|
||||
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(lang.file);
|
||||
// Check for missing translations from the bundled file.
|
||||
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
|
||||
private @NotNull LangLocation bestMatch(@NotNull String locale, @Nullable LangLocation initial) {
|
||||
File file = new File(folder, locale + ".yml");
|
||||
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
|
||||
|
||||
if (newKeys.isEmpty()) {
|
||||
return localeConfig;
|
||||
}
|
||||
|
||||
// Get guess section for missing keys.
|
||||
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
|
||||
|
||||
for (String newKey : newKeys) {
|
||||
// Set all missing keys to defaults.
|
||||
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
|
||||
|
||||
// Delete relevant guess keys in case this is a new translation.
|
||||
if (guess != null) {
|
||||
guess.set(newKey, null);
|
||||
}
|
||||
}
|
||||
|
||||
// If guess section is empty, delete it.
|
||||
if (guess != null && guess.getKeys(false).isEmpty()) {
|
||||
localeConfig.set("guess", null);
|
||||
}
|
||||
|
||||
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + lang.locale + ".yml: " + String.join(", ", newKeys));
|
||||
|
||||
// Write new keys to disk.
|
||||
try {
|
||||
localeConfig.save(lang.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
|
||||
}
|
||||
|
||||
return localeConfig;
|
||||
if (file.exists() || bundled != null) {
|
||||
return new LangLocation(locale, file, bundled);
|
||||
}
|
||||
|
||||
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull YamlConfiguration localeConfig) {
|
||||
YamlConfiguration defaultLocaleConfig = locales.get(defaultLocale);
|
||||
|
||||
// Get missing keys. Keys that already have a guess value are not new and don't need to trigger another write.
|
||||
List<String> missingKeys = getMissingKeys(
|
||||
defaultLocaleConfig,
|
||||
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key));
|
||||
|
||||
if (!missingKeys.isEmpty()) {
|
||||
// Set up guess section for missing keys.
|
||||
for (String key : missingKeys) {
|
||||
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
|
||||
}
|
||||
|
||||
// Write modified guess section to disk.
|
||||
try {
|
||||
localeConfig.save(location.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + location.locale + ".yml");
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through to default locale.
|
||||
localeConfig.setDefaults(defaultLocaleConfig);
|
||||
if (initial == null) {
|
||||
initial = new LangLocation(locale, file, null);
|
||||
}
|
||||
|
||||
private @NotNull List<String> getMissingKeys(
|
||||
@NotNull Configuration configurationDefault,
|
||||
@NotNull Predicate<String> nodeSetPredicate) {
|
||||
List<String> missingKeys = new ArrayList<>();
|
||||
for (String key : configurationDefault.getKeys(true)) {
|
||||
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
|
||||
// Missing keys are non-section keys that fail the predicate.
|
||||
missingKeys.add(key);
|
||||
}
|
||||
}
|
||||
return missingKeys;
|
||||
int lastSeparator = locale.lastIndexOf('_');
|
||||
|
||||
// Must be at least some content before separator.
|
||||
if (lastSeparator < 1) {
|
||||
return initial;
|
||||
}
|
||||
|
||||
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
|
||||
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return bestMatch(locale.substring(0, lastSeparator), initial);
|
||||
}
|
||||
|
||||
value = ChatColor.translateAlternateColorCodes('&', value);
|
||||
|
||||
return value;
|
||||
private @NotNull YamlConfiguration loadLocale(@NotNull LangLocation lang) {
|
||||
YamlConfiguration localeConfigDefaults;
|
||||
if (lang.bundled == null) {
|
||||
localeConfigDefaults = new YamlConfiguration();
|
||||
} else {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(lang.bundled, StandardCharsets.UTF_8))) {
|
||||
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + lang.locale + ".yml");
|
||||
localeConfigDefaults = new YamlConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getValue(@NotNull String key, @Nullable String locale, Replacement @NotNull ... replacements) {
|
||||
String value = getValue(key, locale);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Replacement replacement : replacements) {
|
||||
value = value.replace(replacement.placeholder(), replacement.value());
|
||||
}
|
||||
|
||||
return value;
|
||||
if (!lang.file.exists()) {
|
||||
// If the file does not exist on disk, save bundled defaults.
|
||||
try {
|
||||
localeConfigDefaults.save(lang.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
|
||||
}
|
||||
// Return loaded bundled locale.
|
||||
return localeConfigDefaults;
|
||||
}
|
||||
|
||||
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
|
||||
return getValue(key, getLocale(sender));
|
||||
// If the file does exist on disk, load it.
|
||||
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(lang.file);
|
||||
// Check for missing translations from the bundled file.
|
||||
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
|
||||
|
||||
if (newKeys.isEmpty()) {
|
||||
return localeConfig;
|
||||
}
|
||||
|
||||
public @Nullable String getLocalizedMessage(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String key,
|
||||
Replacement @NotNull ... replacements) {
|
||||
return getValue(key, getLocale(sender), replacements);
|
||||
// Get guess section for missing keys.
|
||||
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
|
||||
|
||||
for (String newKey : newKeys) {
|
||||
// Set all missing keys to defaults.
|
||||
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
|
||||
|
||||
// Delete relevant guess keys in case this is a new translation.
|
||||
if (guess != null) {
|
||||
guess.set(newKey, null);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull String getLocale(@NotNull CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return ((Player) sender).getLocale();
|
||||
} else {
|
||||
return plugin.getConfig().getString("settings.locale", "en");
|
||||
}
|
||||
// If guess section is empty, delete it.
|
||||
if (guess != null && guess.getKeys(false).isEmpty()) {
|
||||
localeConfig.set("guess", null);
|
||||
}
|
||||
|
||||
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
|
||||
String message = getLocalizedMessage(sender, key);
|
||||
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + lang.locale + ".yml: " + String.join(", ", newKeys));
|
||||
|
||||
if (message != null && !message.isEmpty()) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
// Write new keys to disk.
|
||||
try {
|
||||
localeConfig.save(lang.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + lang.locale + ".yml");
|
||||
}
|
||||
|
||||
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull... replacements) {
|
||||
String message = getLocalizedMessage(sender, key, replacements);
|
||||
return localeConfig;
|
||||
}
|
||||
|
||||
if (message != null && !message.isEmpty()) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
private void addTranslationFallthrough(@NotNull LangLocation location, @NotNull YamlConfiguration localeConfig) {
|
||||
YamlConfiguration defaultLocaleConfig = locales.get(defaultLocale);
|
||||
|
||||
// Get missing keys. Keys that already have a guess value are not new and don't need to trigger another write.
|
||||
List<String> missingKeys = getMissingKeys(
|
||||
defaultLocaleConfig,
|
||||
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key)
|
||||
);
|
||||
|
||||
if (!missingKeys.isEmpty()) {
|
||||
// Set up guess section for missing keys.
|
||||
for (String key : missingKeys) {
|
||||
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
|
||||
}
|
||||
|
||||
// Write modified guess section to disk.
|
||||
try {
|
||||
localeConfig.save(location.file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + location.locale + ".yml");
|
||||
}
|
||||
}
|
||||
|
||||
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
|
||||
String message = getLocalizedMessage(player, key);
|
||||
// Fall through to default locale.
|
||||
localeConfig.setDefaults(defaultLocaleConfig);
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
private @NotNull List<String> getMissingKeys(
|
||||
@NotNull Configuration configurationDefault,
|
||||
@NotNull Predicate<String> nodeSetPredicate
|
||||
) {
|
||||
List<String> missingKeys = new ArrayList<>();
|
||||
for (String key : configurationDefault.getKeys(true)) {
|
||||
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
|
||||
// Missing keys are non-section keys that fail the predicate.
|
||||
missingKeys.add(key);
|
||||
}
|
||||
}
|
||||
return missingKeys;
|
||||
}
|
||||
|
||||
int newline = message.indexOf('\n');
|
||||
if (newline != -1) {
|
||||
// No newlines in action bar chat.
|
||||
message = message.substring(0, newline);
|
||||
}
|
||||
|
||||
if (message.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
|
||||
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
|
||||
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
|
||||
value = ChatColor.translateAlternateColorCodes('&', value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public @Nullable String getValue(
|
||||
@NotNull String key,
|
||||
@Nullable String locale,
|
||||
Replacement @NotNull ... replacements
|
||||
) {
|
||||
String value = getValue(key, locale);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Replacement replacement : replacements) {
|
||||
value = value.replace(replacement.placeholder(), replacement.value());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public @Nullable String getLocalizedMessage(@NotNull CommandSender sender, @NotNull String key) {
|
||||
return getValue(key, getLocale(sender));
|
||||
}
|
||||
|
||||
public @Nullable String getLocalizedMessage(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String key,
|
||||
Replacement @NotNull ... replacements
|
||||
) {
|
||||
return getValue(key, getLocale(sender), replacements);
|
||||
}
|
||||
|
||||
private @NotNull String getLocale(@NotNull CommandSender sender) {
|
||||
if (sender instanceof Player player) {
|
||||
return player.getLocale();
|
||||
} else {
|
||||
return plugin.getConfig().getString("settings.locale", "en");
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(@NotNull CommandSender sender, @NotNull String key) {
|
||||
String message = getLocalizedMessage(sender, key);
|
||||
|
||||
if (message != null && !message.isEmpty()) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull ... replacements) {
|
||||
String message = getLocalizedMessage(sender, key, replacements);
|
||||
|
||||
if (message != null && !message.isEmpty()) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendSystemMessage(@NotNull Player player, @NotNull String key) {
|
||||
String message = getLocalizedMessage(player, key);
|
||||
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int newline = message.indexOf('\n');
|
||||
if (newline != -1) {
|
||||
// No newlines in action bar chat.
|
||||
message = message.substring(0, newline);
|
||||
}
|
||||
|
||||
if (message.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacy(message));
|
||||
}
|
||||
|
||||
private record LangLocation(@NotNull String locale, @NotNull File file, @Nullable InputStream bundled) {}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Project meta
|
||||
group=com.lishid.openinv
|
||||
version=5.1.10
|
||||
version=5.1.13
|
||||
description=A Bukkit plugin for opening normally-inaccessible inventories.
|
||||
|
||||
# Gradle configuration
|
||||
|
||||
@@ -3,9 +3,12 @@ spigotapi = "1.21.5-R0.1-SNAPSHOT"
|
||||
specialsource = "1.11.5"
|
||||
planarwrappers = "3.3.0"
|
||||
annotations = "26.0.2"
|
||||
paperweight = "2.0.0-beta.16"
|
||||
shadow = "8.3.6"
|
||||
paperweight = "2.0.0-beta.17"
|
||||
shadow = "8.3.8"
|
||||
folia-scheduler-wrapper = "v0.0.3"
|
||||
errorprone-core = "2.39.0"
|
||||
errorprone-gradle = "4.2.0"
|
||||
slf4j = "2.0.17"
|
||||
|
||||
[libraries]
|
||||
spigotapi = { module = "org.spigotmc:spigot-api", version.ref = "spigotapi" }
|
||||
@@ -13,7 +16,11 @@ specialsource = { module = "net.md-5:SpecialSource", version.ref = "specialsourc
|
||||
planarwrappers = { module = "com.github.jikoo:planarwrappers", version.ref = "planarwrappers" }
|
||||
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
|
||||
folia-scheduler-wrapper = { module = "com.github.NahuLD.folia-scheduler-wrapper:folia-scheduler-wrapper", version.ref = "folia-scheduler-wrapper" }
|
||||
errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone-core" }
|
||||
errorprone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorprone-gradle" }
|
||||
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
|
||||
[plugins]
|
||||
paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" }
|
||||
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
|
||||
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
|
||||
errorprone-gradle = { id = "net.ltgt.errorprone", version.ref = "errorprone-gradle" }
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +82,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -133,22 +133,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
@@ -205,6 +216,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+21
-16
@@ -13,8 +13,10 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +27,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -18,8 +18,8 @@ plugins {
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val paper = candidates.firstOrNull {
|
||||
it.id.let {
|
||||
id -> id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
}
|
||||
}
|
||||
if (paper != null) {
|
||||
@@ -33,7 +33,7 @@ dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
|
||||
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT")
|
||||
paperweight.paperDevBundle("1.21.8-R0.1-SNAPSHOT")
|
||||
}
|
||||
|
||||
val spigot = tasks.register<ShadowJar>("spigotRelocations") {
|
||||
|
||||
+146
-140
@@ -27,6 +27,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.level.ServerPlayerGameMode;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.inventory.ChestMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.PlayerEnderChestContainer;
|
||||
@@ -52,159 +53,164 @@ import java.util.logging.Logger;
|
||||
|
||||
public class AnySilentContainer extends AnySilentContainerBase {
|
||||
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull LanguageManager lang;
|
||||
private @Nullable Field serverPlayerGameModeGameType;
|
||||
private final @NotNull Logger logger;
|
||||
private final @NotNull LanguageManager lang;
|
||||
private @Nullable Field serverPlayerGameModeGameType;
|
||||
|
||||
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
this.logger = logger;
|
||||
this.lang = lang;
|
||||
try {
|
||||
try {
|
||||
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("gameModeForPlayer");
|
||||
this.serverPlayerGameModeGameType.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
logger.warning("The field ServerPlayerGameMode#gameModeForPlayer is no longer present!");
|
||||
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
|
||||
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
|
||||
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
|
||||
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
|
||||
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
|
||||
}
|
||||
public AnySilentContainer(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
this.logger = logger;
|
||||
this.lang = lang;
|
||||
try {
|
||||
try {
|
||||
this.serverPlayerGameModeGameType = ServerPlayerGameMode.class.getDeclaredField("gameModeForPlayer");
|
||||
this.serverPlayerGameModeGameType.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
logger.warning("The field ServerPlayerGameMode#gameModeForPlayer is no longer present!");
|
||||
logger.warning("Please report this at https://github.com/Jikoo/OpenInv/issues");
|
||||
logger.warning("Attempting to fall through using reflection. Please verify that SilentContainer does not fail.");
|
||||
// N.B. gameModeForPlayer is (for now) declared before previousGameModeForPlayer so silent shouldn't break.
|
||||
this.serverPlayerGameModeGameType = ReflectionHelper.grabFieldByType(ServerPlayerGameMode.class, GameType.class);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
logger.warning("Unable to directly write player game mode! SilentContainer will fail.");
|
||||
logger.log(java.util.logging.Level.WARNING, "Error obtaining GameType field", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateContainer(
|
||||
@NotNull final Player bukkitPlayer,
|
||||
final boolean silentchest,
|
||||
@NotNull final org.bukkit.block.Block bukkitBlock
|
||||
) {
|
||||
|
||||
// Silent ender chest is API-only
|
||||
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
|
||||
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
|
||||
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateContainer(
|
||||
@NotNull final Player bukkitPlayer,
|
||||
final boolean silentchest,
|
||||
@NotNull final org.bukkit.block.Block bukkitBlock) {
|
||||
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
|
||||
|
||||
// Silent ender chest is API-only
|
||||
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
|
||||
bukkitPlayer.openInventory(bukkitPlayer.getEnderChest());
|
||||
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
|
||||
return true;
|
||||
}
|
||||
final net.minecraft.world.level.Level level = ((Entity) player).level();
|
||||
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
|
||||
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
|
||||
|
||||
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
|
||||
if (blockEntity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final net.minecraft.world.level.Level level = player.level();
|
||||
final BlockPos blockPos = new BlockPos(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ());
|
||||
final BlockEntity blockEntity = level.getBlockEntity(blockPos);
|
||||
|
||||
if (blockEntity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
|
||||
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
|
||||
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
|
||||
enderChest.setActiveChest(enderChestTile);
|
||||
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
|
||||
if (blockEntity instanceof EnderChestBlockEntity enderChestTile) {
|
||||
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
|
||||
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
|
||||
enderChest.setActiveChest(enderChestTile);
|
||||
player.openMenu(
|
||||
new SimpleMenuProvider(
|
||||
(containerCounter, playerInventory, ignored) -> {
|
||||
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
|
||||
int rows = enderChest.getContainerSize() / 9;
|
||||
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
|
||||
}, Component.translatable("container.enderchest")));
|
||||
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(blockEntity instanceof MenuProvider menuProvider)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockState blockState = level.getBlockState(blockPos);
|
||||
Block block = blockState.getBlock();
|
||||
|
||||
if (block instanceof ChestBlock chestBlock) {
|
||||
|
||||
// boolean flag: do not check if chest is blocked
|
||||
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
|
||||
|
||||
if (menuProvider == null) {
|
||||
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block instanceof TrappedChestBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
|
||||
} else {
|
||||
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof ShulkerBoxBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
|
||||
}
|
||||
|
||||
if (block instanceof BarrelBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
|
||||
}
|
||||
|
||||
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
|
||||
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
|
||||
player.openMenu(menuProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
// SilentChest requires access to setting players' game mode directly.
|
||||
if (this.serverPlayerGameModeGameType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
|
||||
if (lootable.lootTable != null) {
|
||||
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GameType gameType = player.gameMode.getGameModeForPlayer();
|
||||
this.forceGameType(player, GameType.SPECTATOR);
|
||||
player.openMenu(menuProvider);
|
||||
this.forceGameType(player, gameType);
|
||||
return true;
|
||||
}, Component.translatable("container.enderchest")
|
||||
)
|
||||
);
|
||||
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
|
||||
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
|
||||
|
||||
// Force game mode change without informing plugins or players.
|
||||
// Regular game mode set calls GameModeChangeEvent and is cancellable.
|
||||
GameType gameType = player.gameMode.getGameModeForPlayer();
|
||||
this.forceGameType(player, GameType.SPECTATOR);
|
||||
|
||||
// ServerPlayer#closeContainer cannot be called without entering an
|
||||
// infinite loop because this method is called during inventory close.
|
||||
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
|
||||
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
|
||||
// From ServerPlayer#closeContainer
|
||||
player.doCloseContainer();
|
||||
// Regular inventory close will handle the rest - packet sending, etc.
|
||||
|
||||
// Revert forced game mode.
|
||||
this.forceGameType(player, gameType);
|
||||
if (!(blockEntity instanceof MenuProvider menuProvider)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
|
||||
if (this.serverPlayerGameModeGameType == null) {
|
||||
// No need to warn repeatedly, error on startup and lack of function should be enough.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.serverPlayerGameModeGameType.setAccessible(true);
|
||||
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
|
||||
}
|
||||
BlockState blockState = level.getBlockState(blockPos);
|
||||
Block block = blockState.getBlock();
|
||||
|
||||
if (block instanceof ChestBlock chestBlock) {
|
||||
|
||||
// boolean flag: do not check if chest is blocked
|
||||
menuProvider = chestBlock.getMenuProvider(blockState, level, blockPos, true);
|
||||
|
||||
if (menuProvider == null) {
|
||||
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block instanceof TrappedChestBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.TRAPPED_CHEST_TRIGGERED);
|
||||
} else {
|
||||
bukkitPlayer.incrementStatistic(Statistic.CHEST_OPENED);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof ShulkerBoxBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.SHULKER_BOX_OPENED);
|
||||
}
|
||||
|
||||
if (block instanceof BarrelBlock) {
|
||||
bukkitPlayer.incrementStatistic(Statistic.OPEN_BARREL);
|
||||
}
|
||||
|
||||
// AnyChest only - SilentChest not active, container unsupported, or unnecessary.
|
||||
if (!silentchest || player.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) {
|
||||
player.openMenu(menuProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
// SilentChest requires access to setting players' game mode directly.
|
||||
if (this.serverPlayerGameModeGameType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blockEntity instanceof RandomizableContainerBlockEntity lootable) {
|
||||
if (lootable.lootTable != null) {
|
||||
lang.sendSystemMessage(bukkitPlayer, "messages.error.lootNotGenerated");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GameType gameType = player.gameMode.getGameModeForPlayer();
|
||||
this.forceGameType(player, GameType.SPECTATOR);
|
||||
player.openMenu(menuProvider);
|
||||
this.forceGameType(player, gameType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivateContainer(@NotNull final Player bukkitPlayer) {
|
||||
if (this.serverPlayerGameModeGameType == null || bukkitPlayer.getGameMode() == GameMode.SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
|
||||
|
||||
// Force game mode change without informing plugins or players.
|
||||
// Regular game mode set calls GameModeChangeEvent and is cancellable.
|
||||
GameType gameType = player.gameMode.getGameModeForPlayer();
|
||||
this.forceGameType(player, GameType.SPECTATOR);
|
||||
|
||||
// ServerPlayer#closeContainer cannot be called without entering an
|
||||
// infinite loop because this method is called during inventory close.
|
||||
// From ServerPlayer#closeContainer -> CraftEventFactory#handleInventoryCloseEvent
|
||||
player.containerMenu.transferTo(player.inventoryMenu, player.getBukkitEntity());
|
||||
// From ServerPlayer#closeContainer
|
||||
player.doCloseContainer();
|
||||
// Regular inventory close will handle the rest - packet sending, etc.
|
||||
|
||||
// Revert forced game mode.
|
||||
this.forceGameType(player, gameType);
|
||||
}
|
||||
|
||||
private void forceGameType(final ServerPlayer player, final GameType gameMode) {
|
||||
if (this.serverPlayerGameModeGameType == null) {
|
||||
// No need to warn repeatedly, error on startup and lack of function should be enough.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.serverPlayerGameModeGameType.setAccessible(true);
|
||||
this.serverPlayerGameModeGameType.set(player.gameMode, gameMode);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
logger.log(java.util.logging.Level.WARNING, "Error bypassing GameModeChangeEvent", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-3
@@ -71,9 +71,6 @@ public class OpenEnderChest implements Container, StackedContentsCompatible, Int
|
||||
items = activeItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerOffline() {}
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.entity.Player getPlayer() {
|
||||
return owner.getBukkitEntity();
|
||||
|
||||
+25
-23
@@ -49,7 +49,7 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
private final int size;
|
||||
protected ServerPlayer owner;
|
||||
private int maxStackSize = 99;
|
||||
private CraftInventory bukkitEntity;
|
||||
protected CraftInventory bukkitEntity;
|
||||
public List<HumanEntity> transaction = new ArrayList<>();
|
||||
|
||||
public OpenInventory(@NotNull org.bukkit.entity.Player bukkitPlayer) {
|
||||
@@ -70,8 +70,8 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
// If inventory is expected size, we can arrange slots to be pretty.
|
||||
Inventory ownerInv = owner.getInventory();
|
||||
if (ownerInv.getNonEquipmentItems().size() == 36
|
||||
&& Inventory.EQUIPMENT_SLOT_MAPPING.size() == 5
|
||||
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4) {
|
||||
&& owner.inventoryMenu.getCraftSlots().getContainerSize() == 4
|
||||
&& (Inventory.EQUIPMENT_SLOT_MAPPING.size() == 5 || Inventory.EQUIPMENT_SLOT_MAPPING.size() == 7)) {
|
||||
// Armor slots: Bottom left.
|
||||
addArmor(36);
|
||||
// Off-hand: Below chestplate.
|
||||
@@ -112,12 +112,15 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
invIndex = localIndex - hotbarDiff;
|
||||
}
|
||||
|
||||
slots.set(localIndex, new ContentList(owner, invIndex, type) {
|
||||
@Override
|
||||
public void setHolder(@NotNull ServerPlayer holder) {
|
||||
items = holder.getInventory().getNonEquipmentItems();
|
||||
}
|
||||
});
|
||||
slots.set(
|
||||
localIndex,
|
||||
new ContentList(owner, invIndex, type) {
|
||||
@Override
|
||||
public void setHolder(@NotNull ServerPlayer holder) {
|
||||
items = holder.getInventory().getNonEquipmentItems();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return listSize;
|
||||
}
|
||||
@@ -133,8 +136,8 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
.toArray(EquipmentSlot[]::new);
|
||||
int localIndex = 0;
|
||||
for (int i = sorted.length - 1; i >= 0; --i) {
|
||||
// Skip off-hand, handled separately.
|
||||
if (sorted[i] == EquipmentSlot.OFFHAND) {
|
||||
// Skip off-hand, handled separately. Also skip non-player slots.
|
||||
if (sorted[i].getType() != EquipmentSlot.Type.HUMANOID_ARMOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -170,16 +173,17 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
|
||||
if (pretty) {
|
||||
slots.set(startIndex + 2, new ContentViewOnly(owner) {
|
||||
@Override
|
||||
public Slot asSlot(Container container, int slot, int x, int y) {
|
||||
return new SlotViewOnly(container, slot, x, y) {
|
||||
@Override
|
||||
public ItemStack getOrDefault() {
|
||||
return Placeholders.craftingOutput;
|
||||
public Slot asSlot(Container container, int slot, int x, int y) {
|
||||
return new SlotViewOnly(container, slot, x, y) {
|
||||
@Override
|
||||
public ItemStack getOrDefault() {
|
||||
return Placeholders.craftingOutput;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
slots.set(startIndex + 11, getCraftingResult(owner));
|
||||
}
|
||||
|
||||
@@ -244,9 +248,6 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
slots.forEach(slot -> slot.setHolder(newOwner));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerOffline() {}
|
||||
|
||||
@Override
|
||||
public boolean isInUse() {
|
||||
return !transaction.isEmpty();
|
||||
@@ -298,7 +299,8 @@ public class OpenInventory implements Container, InternalOwned<ServerPlayer>, IS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {}
|
||||
public void setChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(@NotNull Player player) {
|
||||
|
||||
+2
-1
@@ -109,7 +109,8 @@ public class OpenDummyInventory extends CraftInventory implements ViewOnly {
|
||||
@SuppressWarnings("NonApiType")
|
||||
@Override
|
||||
public @NotNull HashMap<Integer, ItemStack> all(
|
||||
@NotNull Material material) throws IllegalArgumentException {
|
||||
@NotNull Material material
|
||||
) throws IllegalArgumentException {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -111,6 +111,7 @@ public class OpenDummyPlayerInventory extends OpenDummyInventory implements Play
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInHand() {
|
||||
|
||||
+4
-1
@@ -84,7 +84,8 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
|
||||
for (int index = 0; index < items.length; ++index) {
|
||||
getInventory().getOwnerHandle().getInventory().equipment.set(
|
||||
Inventory.EQUIPMENT_SLOTS_SORTED_BY_INDEX[index],
|
||||
CraftItemStack.asNMSCopy(items[index]));
|
||||
CraftItemStack.asNMSCopy(items[index])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +174,14 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
|
||||
.set(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(item));
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setItemInHand(@Nullable ItemStack stack) {
|
||||
|
||||
+5
-2
@@ -37,6 +37,7 @@ import java.util.function.Supplier;
|
||||
/**
|
||||
* An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}.
|
||||
*/
|
||||
@SuppressWarnings("HidingField") // Revisit when removing 1.21.4 support
|
||||
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
|
||||
extends AbstractContainerMenu {
|
||||
|
||||
@@ -61,7 +62,8 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
|
||||
int containerCounter,
|
||||
@NotNull T container,
|
||||
@NotNull ServerPlayer viewer,
|
||||
boolean viewOnly) {
|
||||
boolean viewOnly
|
||||
) {
|
||||
super(type, containerCounter);
|
||||
this.container = container;
|
||||
this.viewer = viewer;
|
||||
@@ -123,7 +125,8 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
|
||||
};
|
||||
}
|
||||
|
||||
protected void preSlotSetup() {}
|
||||
protected void preSlotSetup() {
|
||||
}
|
||||
|
||||
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
|
||||
Slot slot = new Slot(container, index, x, y);
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@ public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
|
||||
@NotNull OpenEnderChest enderChest,
|
||||
@NotNull ServerPlayer viewer,
|
||||
int containerId,
|
||||
boolean viewOnly) {
|
||||
boolean viewOnly
|
||||
) {
|
||||
super(getChestMenuType(enderChest.getContainerSize()), containerId, enderChest, viewer, viewOnly);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -133,8 +133,11 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
|
||||
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
|
||||
return null;
|
||||
}
|
||||
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
|
||||
"Slot %s outside of inventory", rawSlot);
|
||||
Preconditions.checkArgument(
|
||||
rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
|
||||
"Slot %s outside of inventory",
|
||||
rawSlot
|
||||
);
|
||||
if (rawSlot > topSize) {
|
||||
return getBottomInventory();
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.lishid.openinv.internal.common.container.slot;
|
||||
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.Placeholders;
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
@@ -35,7 +35,7 @@ public class ContentCrafting implements Content {
|
||||
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
|
||||
// it ignores changes without extra help, and will delete the item as a result.
|
||||
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
|
||||
return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
|
||||
return BaseOpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.lishid.openinv.internal.common.container.slot;
|
||||
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.Placeholders;
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -64,7 +64,7 @@ public class ContentCursor implements Content {
|
||||
// Player must be online and not in creative - since the creative client is (semi-)authoritative,
|
||||
// it ignores changes without extra help, and will delete the item as a result.
|
||||
// Spectator mode is technically possible but may cause the item to be dropped if the client opens an inventory.
|
||||
return OpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
|
||||
return BaseOpenPlayer.isConnected(holder.connection) && holder.gameMode.isSurvival();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.lishid.openinv.internal.common.container.slot;
|
||||
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.Placeholders;
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
@@ -64,14 +64,14 @@ public class ContentDrop implements Content {
|
||||
|
||||
@Override
|
||||
public ItemStack getOrDefault() {
|
||||
return OpenPlayer.isConnected(holder.connection)
|
||||
return BaseOpenPlayer.isConnected(holder.connection)
|
||||
? Placeholders.drop
|
||||
: Placeholders.blockedOffline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mayPlace(@NotNull ItemStack itemStack) {
|
||||
return OpenPlayer.isConnected(holder.connection);
|
||||
return BaseOpenPlayer.isConnected(holder.connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-1
@@ -3,7 +3,6 @@ package com.lishid.openinv.internal.common.container.slot;
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.Placeholders;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.entity.EntityEquipment;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
package com.lishid.openinv.internal.common.container.slot;
|
||||
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
@@ -37,13 +37,14 @@ public class ContentOffHand extends ContentEquipment {
|
||||
return new SlotEquipment(container, slot, x, y) {
|
||||
@Override
|
||||
public void setChanged() {
|
||||
if (OpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
|
||||
if (BaseOpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
|
||||
holder.connection.send(
|
||||
new ClientboundContainerSetSlotPacket(
|
||||
holder.inventoryMenu.containerId,
|
||||
holder.inventoryMenu.incrementStateId(),
|
||||
InventoryMenu.SHIELD_SLOT,
|
||||
holder.getOffhandItem()));
|
||||
holder.getOffhandItem()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+35
-32
@@ -1,10 +1,12 @@
|
||||
package com.lishid.openinv.internal.common.container.slot.placeholder;
|
||||
|
||||
import com.mojang.serialization.DataResult;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.DyeColor;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -21,50 +23,40 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class PlaceholderLoaderBase {
|
||||
|
||||
protected PlaceholderLoaderBase() {
|
||||
public void load(@Nullable ConfigurationSection section) throws Exception {
|
||||
Placeholders.craftingOutput = parse(section, "crafting-output", defaultCraftingOutput());
|
||||
Placeholders.cursor = parse(section, "cursor", defaultCursor());
|
||||
Placeholders.drop = parse(section, "drop", defaultDrop());
|
||||
Placeholders.emptyHelmet = parse(section, "empty-helmet", getEmptyArmor(Items.LEATHER_HELMET));
|
||||
Placeholders.emptyChestplate = parse(section, "empty-chestplate", getEmptyArmor(Items.LEATHER_CHESTPLATE));
|
||||
Placeholders.emptyLeggings = parse(section, "empty-leggings", getEmptyArmor(Items.LEATHER_LEGGINGS));
|
||||
Placeholders.emptyBoots = parse(section, "empty-boots", getEmptyArmor(Items.LEATHER_BOOTS));
|
||||
Placeholders.emptyOffHand = parse(section, "empty-off-hand", defaultShield());
|
||||
Placeholders.notSlot = parse(section, "not-a-slot", defaultNotSlot());
|
||||
Placeholders.blockedOffline = parse(section, "blocked.offline", defaultBlockedOffline());
|
||||
|
||||
for (GameType type : GameType.values()) {
|
||||
// Barrier: "Not available - Creative" etc.
|
||||
ItemStack typeItem = new ItemStack(Items.BARRIER);
|
||||
typeItem.set(
|
||||
DataComponents.ITEM_NAME,
|
||||
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName()));
|
||||
Component.translatable("options.narrator.notavailable").append(" - ").append(type.getShortDisplayName())
|
||||
);
|
||||
Placeholders.BLOCKED_GAME_TYPE.put(type, typeItem);
|
||||
}
|
||||
Placeholders.craftingOutput = defaultCraftingOutput();
|
||||
Placeholders.cursor = defaultCursor();
|
||||
Placeholders.drop = defaultDrop();
|
||||
Placeholders.emptyHelmet = getEmptyArmor(Items.LEATHER_HELMET);
|
||||
Placeholders.emptyChestplate = getEmptyArmor(Items.LEATHER_CHESTPLATE);
|
||||
Placeholders.emptyLeggings = getEmptyArmor(Items.LEATHER_LEGGINGS);
|
||||
Placeholders.emptyBoots = getEmptyArmor(Items.LEATHER_BOOTS);
|
||||
Placeholders.emptyOffHand = defaultShield();
|
||||
Placeholders.notSlot = defaultNotSlot();
|
||||
Placeholders.blockedOffline = defaultBlockedOffline();
|
||||
}
|
||||
|
||||
public void load(@Nullable ConfigurationSection section) throws Exception {
|
||||
Placeholders.craftingOutput = parse(section, "crafting-output", Placeholders.craftingOutput);
|
||||
Placeholders.cursor = parse(section, "cursor", Placeholders.cursor);
|
||||
Placeholders.drop = parse(section, "drop", Placeholders.drop);
|
||||
Placeholders.emptyHelmet = parse(section, "empty-helmet", Placeholders.emptyHelmet);
|
||||
Placeholders.emptyChestplate = parse(section, "empty-chestplate", Placeholders.emptyChestplate);
|
||||
Placeholders.emptyLeggings = parse(section, "empty-leggings", Placeholders.emptyLeggings);
|
||||
Placeholders.emptyBoots = parse(section, "empty-boots", Placeholders.emptyBoots);
|
||||
Placeholders.emptyOffHand = parse(section, "empty-off-hand", Placeholders.emptyOffHand);
|
||||
Placeholders.notSlot = parse(section, "not-a-slot", Placeholders.notSlot);
|
||||
Placeholders.blockedOffline = parse(section, "blocked.offline", Placeholders.blockedOffline);
|
||||
Placeholders.BLOCKED_GAME_TYPE.put(GameType.CREATIVE, parse(section, "blocked.creative", Placeholders.BLOCKED_GAME_TYPE.get(GameType.CREATIVE)));
|
||||
Placeholders.BLOCKED_GAME_TYPE.put(GameType.SPECTATOR, parse(section, "blocked.spectator", Placeholders.BLOCKED_GAME_TYPE.get(GameType.SPECTATOR)));
|
||||
}
|
||||
|
||||
private @NotNull ItemStack parse(
|
||||
protected @NotNull ItemStack parse(
|
||||
@Nullable ConfigurationSection section,
|
||||
@NotNull String path,
|
||||
@NotNull ItemStack defaultStack) throws Exception {
|
||||
@NotNull ItemStack defaultStack
|
||||
) throws Exception {
|
||||
if (section == null) {
|
||||
return defaultStack;
|
||||
}
|
||||
@@ -76,8 +68,14 @@ public abstract class PlaceholderLoaderBase {
|
||||
}
|
||||
|
||||
CompoundTag compoundTag = parseTag(itemText);
|
||||
Optional<ItemStack> parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag);
|
||||
return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack);
|
||||
DataResult<ItemStack> parsed = ItemStack.CODEC.parse(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), compoundTag);
|
||||
ItemStack itemStack;
|
||||
try {
|
||||
itemStack = parsed.getOrThrow();
|
||||
} catch (Exception e) {
|
||||
itemStack = null;
|
||||
}
|
||||
return itemStack == null ? defaultStack : itemStack;
|
||||
}
|
||||
|
||||
protected abstract @NotNull CompoundTag parseTag(@NotNull String itemText) throws Exception;
|
||||
@@ -108,7 +106,9 @@ public abstract class PlaceholderLoaderBase {
|
||||
new BannerPatternLayers(List.of(
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY))));
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY)
|
||||
))
|
||||
);
|
||||
addModelData(itemStack);
|
||||
hideTooltip(itemStack);
|
||||
return itemStack;
|
||||
@@ -150,7 +150,9 @@ public abstract class PlaceholderLoaderBase {
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK))));
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK)
|
||||
))
|
||||
);
|
||||
hideTooltip(itemStack);
|
||||
addModelData(itemStack);
|
||||
return itemStack;
|
||||
@@ -170,7 +172,8 @@ public abstract class PlaceholderLoaderBase {
|
||||
itemStack.set(DataComponents.ITEM_NAME,
|
||||
Component.translatable("options.narrator.notavailable")
|
||||
.append(Component.literal(" - "))
|
||||
.append(Component.translatable("gui.socialInteractions.status_offline")));
|
||||
.append(Component.translatable("gui.socialInteractions.status_offline"))
|
||||
);
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.lishid.openinv.internal.common.container.slot.placeholder;
|
||||
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.GameType;
|
||||
@@ -23,7 +23,7 @@ public final class Placeholders {
|
||||
public static @NotNull ItemStack blockedOffline = ItemStack.EMPTY;
|
||||
|
||||
public static ItemStack survivalOnly(@NotNull ServerPlayer serverPlayer) {
|
||||
if (!OpenPlayer.isConnected(serverPlayer.connection)) {
|
||||
if (!BaseOpenPlayer.isConnected(serverPlayer.connection)) {
|
||||
return blockedOffline;
|
||||
}
|
||||
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package com.lishid.openinv.internal.common.player;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NumericTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.level.storage.ValueOutput;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseOpenPlayer extends CraftPlayer {
|
||||
|
||||
/**
|
||||
* List of tags to always reset when saving.
|
||||
*
|
||||
* @see net.minecraft.world.entity.Entity#saveWithoutId(ValueOutput)
|
||||
* @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(ValueOutput)
|
||||
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(ValueOutput)
|
||||
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(ValueOutput)
|
||||
*/
|
||||
@Unmodifiable
|
||||
protected static final Set<String> RESET_TAGS = Set.of(
|
||||
// Entity#saveWithoutId(CompoundTag)
|
||||
"CustomName",
|
||||
"CustomNameVisible",
|
||||
"Silent",
|
||||
"NoGravity",
|
||||
"Glowing",
|
||||
"TicksFrozen",
|
||||
"HasVisualFire",
|
||||
"Tags",
|
||||
"Passengers",
|
||||
// ServerPlayer#addAdditionalSaveData(CompoundTag)
|
||||
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
|
||||
"warden_spawn_tracker",
|
||||
"entered_nether_pos", // Replaces enteredNetherPosition
|
||||
"enteredNetherPosition",
|
||||
"respawn", // Replaces SpawnXyz fields as of 1.21.6
|
||||
"SpawnX",
|
||||
"SpawnY",
|
||||
"SpawnZ",
|
||||
"SpawnForced",
|
||||
"SpawnAngle",
|
||||
"SpawnDimension",
|
||||
"raid_omen_position",
|
||||
"ender_pearls",
|
||||
// Player#addAdditionalSaveData(CompoundTag)
|
||||
"ShoulderEntityLeft",
|
||||
"ShoulderEntityRight",
|
||||
"LastDeathLocation",
|
||||
"current_explosion_impact_pos",
|
||||
// LivingEntity#addAdditionalSaveData(CompoundTag)
|
||||
"active_effects",
|
||||
"sleeping_pos", // Replaces SleepingXyz fields as of 1.21.6
|
||||
"SleepingX",
|
||||
"SleepingY",
|
||||
"SleepingZ",
|
||||
"Brain",
|
||||
"last_hurt_by_player",
|
||||
"last_hurt_by_player_memory_time",
|
||||
"last_hurt_by_mob",
|
||||
"ticks_since_last_hurt_by_mob",
|
||||
"equipment",
|
||||
"locator_bar_icon"
|
||||
);
|
||||
|
||||
private final PlayerManager manager;
|
||||
|
||||
protected BaseOpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
|
||||
super(server, entity);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData() {
|
||||
manager.loadData(server.getServer(), getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void saveData();
|
||||
|
||||
@Contract("null -> new")
|
||||
protected @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
|
||||
if (oldData == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
|
||||
// Copy old data. This is a deep clone, so operating on it should be safe.
|
||||
oldData = oldData.copy();
|
||||
|
||||
// Remove vanilla/server data that is not written every time.
|
||||
oldData.keySet().removeIf(
|
||||
key -> RESET_TAGS.contains(key)
|
||||
|| key.startsWith("Bukkit")
|
||||
|| (key.startsWith("Paper") && key.length() > 5)
|
||||
);
|
||||
|
||||
return oldData;
|
||||
}
|
||||
|
||||
protected void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) {
|
||||
// Revert automatic updates to play timestamps.
|
||||
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
|
||||
}
|
||||
|
||||
private <T extends Tag> void copyValue(
|
||||
@NotNull CompoundTag source,
|
||||
@NotNull CompoundTag target,
|
||||
@NotNull String container,
|
||||
@NotNull String key,
|
||||
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType
|
||||
) {
|
||||
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
|
||||
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
|
||||
|
||||
// New container being null means the server implementation doesn't store this data.
|
||||
if (newContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If old tag exists, copy it to new location, removing otherwise.
|
||||
setTag(newContainer, key, getTag(oldContainer, key, tagType));
|
||||
}
|
||||
|
||||
private <T extends Tag> @Nullable T getTag(
|
||||
@Nullable CompoundTag container,
|
||||
@NotNull String key,
|
||||
@NotNull Class<T> dataType
|
||||
) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
Tag value = container.get(key);
|
||||
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
|
||||
return null;
|
||||
}
|
||||
return dataType.cast(value);
|
||||
}
|
||||
|
||||
private <T extends Tag> void setTag(
|
||||
@NotNull CompoundTag container,
|
||||
@NotNull String key,
|
||||
@Nullable T data
|
||||
) {
|
||||
if (data == null) {
|
||||
container.remove(key);
|
||||
} else {
|
||||
container.put(key, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isConnected(@Nullable ServerGamePacketListenerImpl connection) {
|
||||
return connection != null && !connection.isDisconnected();
|
||||
}
|
||||
|
||||
}
|
||||
+20
-145
@@ -5,85 +5,25 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.NumericTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.level.storage.PlayerDataStorage;
|
||||
import net.minecraft.world.level.storage.TagValueOutput;
|
||||
import net.minecraft.world.level.storage.ValueOutput;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenPlayer extends CraftPlayer {
|
||||
public class OpenPlayer extends BaseOpenPlayer {
|
||||
|
||||
/**
|
||||
* List of tags to always reset when saving.
|
||||
*
|
||||
* @see net.minecraft.world.entity.Entity#saveWithoutId(CompoundTag)
|
||||
* @see net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag)
|
||||
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag)
|
||||
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag)
|
||||
*/
|
||||
@Unmodifiable
|
||||
protected static final Set<String> RESET_TAGS = Set.of(
|
||||
// Entity#saveWithoutId(CompoundTag)
|
||||
"CustomName",
|
||||
"CustomNameVisible",
|
||||
"Silent",
|
||||
"NoGravity",
|
||||
"Glowing",
|
||||
"TicksFrozen",
|
||||
"HasVisualFire",
|
||||
"Tags",
|
||||
"Passengers",
|
||||
// ServerPlayer#addAdditionalSaveData(CompoundTag)
|
||||
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
|
||||
// As of 1.21.5 only ender_pearls tag still needs to be reset. Rest are for backwards compatibility.
|
||||
"warden_spawn_tracker",
|
||||
"enteredNetherPosition",
|
||||
"SpawnX",
|
||||
"SpawnY",
|
||||
"SpawnZ",
|
||||
"SpawnForced",
|
||||
"SpawnAngle",
|
||||
"SpawnDimension",
|
||||
"raid_omen_position",
|
||||
"ender_pearls",
|
||||
// Player#addAdditionalSaveData(CompoundTag)
|
||||
"ShoulderEntityLeft",
|
||||
"ShoulderEntityRight",
|
||||
"LastDeathLocation",
|
||||
"current_explosion_impact_pos", // Unnecessary in 1.21.5
|
||||
// LivingEntity#addAdditionalSaveData(CompoundTag)
|
||||
"active_effects",
|
||||
"SleepingX",
|
||||
"SleepingY",
|
||||
"SleepingZ",
|
||||
"Brain",
|
||||
"last_hurt_by_player",
|
||||
"last_hurt_by_player_memory_time",
|
||||
"last_hurt_by_mob",
|
||||
"ticks_since_last_hurt_by_mob",
|
||||
"equipment"
|
||||
);
|
||||
|
||||
private final PlayerManager manager;
|
||||
|
||||
protected OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
|
||||
super(server, entity);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData() {
|
||||
manager.loadData(getHandle());
|
||||
protected OpenPlayer(
|
||||
CraftServer server,
|
||||
ServerPlayer entity,
|
||||
PlayerManager manager
|
||||
) {
|
||||
super(server, entity, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,14 +33,18 @@ public class OpenPlayer extends CraftPlayer {
|
||||
}
|
||||
|
||||
ServerPlayer player = this.getHandle();
|
||||
Logger logger = LogUtils.getLogger();
|
||||
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
|
||||
try {
|
||||
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
|
||||
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), logger)) {
|
||||
PlayerDataStorage worldNBTStorage = server.getServer().getPlayerList().playerIo;
|
||||
|
||||
CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null);
|
||||
CompoundTag oldData = isOnline()
|
||||
? null
|
||||
: worldNBTStorage.load(player.getName().getString(), player.getStringUUID(), scopedCollector).orElse(null);
|
||||
CompoundTag playerData = getWritableTag(oldData);
|
||||
playerData = player.saveWithoutId(playerData);
|
||||
setExtraData(playerData);
|
||||
|
||||
ValueOutput valueOutput = TagValueOutput.createWrappingWithContext(scopedCollector, player.registryAccess(), playerData);
|
||||
player.saveWithoutId(valueOutput);
|
||||
|
||||
if (oldData != null) {
|
||||
// Revert certain special data values when offline.
|
||||
@@ -118,73 +62,4 @@ public class OpenPlayer extends CraftPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null -> new")
|
||||
protected @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
|
||||
if (oldData == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
|
||||
// Copy old data. This is a deep clone, so operating on it should be safe.
|
||||
oldData = oldData.copy();
|
||||
|
||||
// Remove vanilla/server data that is not written every time.
|
||||
oldData.keySet().removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit") || key.startsWith("Paper") && key.length() > 5);
|
||||
|
||||
return oldData;
|
||||
}
|
||||
|
||||
private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) {
|
||||
// Revert automatic updates to play timestamps.
|
||||
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
|
||||
}
|
||||
|
||||
private <T extends Tag> void copyValue(
|
||||
@NotNull CompoundTag source,
|
||||
@NotNull CompoundTag target,
|
||||
@NotNull String container,
|
||||
@NotNull String key,
|
||||
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType) {
|
||||
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
|
||||
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
|
||||
|
||||
// New container being null means the server implementation doesn't store this data.
|
||||
if (newContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If old tag exists, copy it to new location, removing otherwise.
|
||||
setTag(newContainer, key, getTag(oldContainer, key, tagType));
|
||||
}
|
||||
|
||||
private <T extends Tag> @Nullable T getTag(
|
||||
@Nullable CompoundTag container,
|
||||
@NotNull String key,
|
||||
@NotNull Class<T> dataType) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
Tag value = container.get(key);
|
||||
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
|
||||
return null;
|
||||
}
|
||||
return dataType.cast(value);
|
||||
}
|
||||
|
||||
private <T extends Tag> void setTag(
|
||||
@NotNull CompoundTag container,
|
||||
@NotNull String key,
|
||||
@Nullable T data) {
|
||||
if (data == null) {
|
||||
container.remove(key);
|
||||
} else {
|
||||
container.put(key, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isConnected(@Nullable ServerGamePacketListenerImpl connection) {
|
||||
return connection != null && !connection.isDisconnected();
|
||||
}
|
||||
|
||||
}
|
||||
+62
-53
@@ -3,11 +3,9 @@ package com.lishid.openinv.internal.common.player;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.common.container.OpenEnderChest;
|
||||
import com.lishid.openinv.internal.common.container.OpenInventory;
|
||||
import com.lishid.openinv.util.JulLoggerAdapter;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import io.papermc.paper.adventure.PaperAdventure;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -15,11 +13,12 @@ import net.minecraft.server.level.ClientInformation;
|
||||
import net.minecraft.server.level.ParticleStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.ChatVisiblity;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.storage.ValueInput;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Server;
|
||||
@@ -55,15 +54,15 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
|
||||
public static @NotNull ServerPlayer getHandle(final Player player) {
|
||||
if (player instanceof CraftPlayer) {
|
||||
return ((CraftPlayer) player).getHandle();
|
||||
if (player instanceof CraftPlayer craftPlayer) {
|
||||
return craftPlayer.getHandle();
|
||||
}
|
||||
|
||||
Server server = player.getServer();
|
||||
ServerPlayer nmsPlayer = null;
|
||||
|
||||
if (server instanceof CraftServer) {
|
||||
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
|
||||
if (server instanceof CraftServer craftServer) {
|
||||
nmsPlayer = craftServer.getHandle().getPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
if (nmsPlayer == null) {
|
||||
@@ -76,7 +75,11 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
|
||||
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftServer server = craftServer.getServer();
|
||||
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
|
||||
|
||||
if (worldServer == null) {
|
||||
@@ -90,7 +93,7 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
entity.getAdvancements().stopListening();
|
||||
|
||||
// Try to load the player's data.
|
||||
if (loadData(entity)) {
|
||||
if (loadData(server, entity)) {
|
||||
// If data is loaded successfully, return the Bukkit entity.
|
||||
return entity.getBukkitEntity();
|
||||
}
|
||||
@@ -101,11 +104,13 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
protected @NotNull ServerPlayer createNewPlayer(
|
||||
@NotNull MinecraftServer server,
|
||||
@NotNull ServerLevel worldServer,
|
||||
@NotNull final OfflinePlayer offline) {
|
||||
@NotNull final OfflinePlayer offline
|
||||
) {
|
||||
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
|
||||
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
|
||||
GameProfile profile = new GameProfile(offline.getUniqueId(),
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()
|
||||
);
|
||||
|
||||
ClientInformation dummyInfo = new ClientInformation(
|
||||
"en_us",
|
||||
@@ -122,40 +127,42 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
|
||||
|
||||
try {
|
||||
injectPlayer(entity);
|
||||
injectPlayer(server, entity);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
boolean loadData(@NotNull ServerPlayer player) {
|
||||
protected boolean loadData(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
|
||||
// See CraftPlayer#loadData
|
||||
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null);
|
||||
|
||||
if (loadedData == null) {
|
||||
// Exceptions with loading are logged by Mojang.
|
||||
return false;
|
||||
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
|
||||
ValueInput loadedData = server.getPlayerList().playerIo.load(player, scopedCollector).orElse(null);
|
||||
|
||||
if (loadedData == null) {
|
||||
// Exceptions with loading are logged.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read basic data into the player.
|
||||
player.load(loadedData);
|
||||
// Game type settings are loaded separately.
|
||||
player.loadGameTypes(loadedData);
|
||||
|
||||
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
|
||||
parseWorld(server, player, loadedData);
|
||||
}
|
||||
|
||||
// Read basic data into the player.
|
||||
player.load(loadedData);
|
||||
// Also read "extra" data.
|
||||
player.readAdditionalSaveData(loadedData);
|
||||
// Game type settings are also loaded separately.
|
||||
player.loadGameTypes(loadedData);
|
||||
|
||||
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
|
||||
parseWorld(player, loadedData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
|
||||
private void parseWorld(@NotNull MinecraftServer server, @NotNull ServerPlayer player, @NotNull ValueInput loadedData) {
|
||||
// See PlayerList#placeNewPlayer
|
||||
World bukkitWorld;
|
||||
Optional<Long> msbs = loadedData.getLong("WorldUUIDMost");
|
||||
@@ -164,29 +171,17 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
// Modern Bukkit world.
|
||||
bukkitWorld = Bukkit.getServer().getWorld(new UUID(msbs.get(), lsbs.get()));
|
||||
} else {
|
||||
Optional<String> worldName = loadedData.getString("world");
|
||||
if (worldName.isPresent()) {
|
||||
// Legacy Bukkit world.
|
||||
bukkitWorld = Bukkit.getServer().getWorld(worldName.get());
|
||||
} else {
|
||||
// Vanilla player data.
|
||||
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
|
||||
.resultOrPartial(logger::warning)
|
||||
.map(player.server::getLevel)
|
||||
// If ServerLevel exists, set, otherwise move to spawn.
|
||||
.ifPresentOrElse(player::setServerLevel, () -> spawnInDefaultWorld(player));
|
||||
return;
|
||||
}
|
||||
bukkitWorld = loadedData.getString("world").map(Bukkit::getWorld).orElse(null);
|
||||
}
|
||||
if (bukkitWorld == null) {
|
||||
spawnInDefaultWorld(player);
|
||||
spawnInDefaultWorld(server, player);
|
||||
return;
|
||||
}
|
||||
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
|
||||
}
|
||||
|
||||
protected void spawnInDefaultWorld(ServerPlayer player) {
|
||||
ServerLevel level = player.server.getLevel(Level.OVERWORLD);
|
||||
protected void spawnInDefaultWorld(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
|
||||
ServerLevel level = server.getLevel(Level.OVERWORLD);
|
||||
if (level != null) {
|
||||
// Adjust player to default spawn (in keeping with Paper handling) when world not found.
|
||||
player.snapTo(player.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), level.getSharedSpawnAngle(), 0.0F);
|
||||
@@ -196,39 +191,53 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
}
|
||||
|
||||
protected void injectPlayer(ServerPlayer player) throws IllegalAccessException {
|
||||
protected void injectPlayer(@NotNull MinecraftServer server, @NotNull ServerPlayer player) throws IllegalAccessException {
|
||||
if (bukkitEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
bukkitEntity.setAccessible(true);
|
||||
|
||||
bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this));
|
||||
bukkitEntity.set(player, new OpenPlayer(server.server, player, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player inject(@NotNull Player player) {
|
||||
try {
|
||||
ServerPlayer nmsPlayer = getHandle(player);
|
||||
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
|
||||
if (nmsPlayer.getBukkitEntity() instanceof BaseOpenPlayer openPlayer) {
|
||||
return openPlayer;
|
||||
}
|
||||
injectPlayer(nmsPlayer);
|
||||
MinecraftServer server = nmsPlayer.getServer();
|
||||
if (server == null) {
|
||||
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
|
||||
logger.warning(() ->
|
||||
"Unable to inject ServerPlayer, certain player data may be lost when saving! Server is not a CraftServer: "
|
||||
+ Bukkit.getServer().getClass().getName());
|
||||
return player;
|
||||
}
|
||||
server = craftServer.getServer();
|
||||
}
|
||||
injectPlayer(server, nmsPlayer);
|
||||
return nmsPlayer.getBukkitEntity();
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
public @Nullable InventoryView openInventory(
|
||||
@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory,
|
||||
boolean viewOnly
|
||||
) {
|
||||
ServerPlayer player = getHandle(bukkitPlayer);
|
||||
|
||||
if (!OpenPlayer.isConnected(player.connection)) {
|
||||
if (!BaseOpenPlayer.isConnected(player.connection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ plugins {
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val paper = candidates.firstOrNull {
|
||||
it.id.let {
|
||||
id -> id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
}
|
||||
}
|
||||
if (paper != null) {
|
||||
@@ -21,6 +21,7 @@ dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
implementation(project(":openinvadaptercommon"))
|
||||
implementation(project(":openinvadapterpaper1_21_5"))
|
||||
implementation(project(":openinvadapterpaper1_21_4"))
|
||||
implementation(project(":openinvadapterpaper1_21_3"))
|
||||
|
||||
|
||||
+20
-11
@@ -28,11 +28,16 @@ public class PlaceholderLoader extends NumericDataPlaceholderLoader {
|
||||
BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT);
|
||||
BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT);
|
||||
BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER);
|
||||
itemStack.set(DataComponents.BANNER_PATTERNS,
|
||||
new BannerPatternLayers(List.of(
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY))));
|
||||
itemStack.set(
|
||||
DataComponents.BANNER_PATTERNS,
|
||||
new BannerPatternLayers(
|
||||
List.of(
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfDiagBottomRight), DyeColor.GRAY),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(downRight), DyeColor.WHITE),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(border), DyeColor.GRAY)
|
||||
)
|
||||
)
|
||||
);
|
||||
addModelData(itemStack);
|
||||
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
|
||||
return itemStack;
|
||||
@@ -51,12 +56,16 @@ public class PlaceholderLoader extends NumericDataPlaceholderLoader {
|
||||
BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT);
|
||||
BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT);
|
||||
itemStack.set(DataComponents.BANNER_PATTERNS,
|
||||
new BannerPatternLayers(List.of(
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK))));
|
||||
new BannerPatternLayers(
|
||||
List.of(
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(halfLeft), DyeColor.BLACK),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomLeft), DyeColor.MAGENTA),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(topRight), DyeColor.BLACK),
|
||||
new BannerPatternLayers.Layer(bannerPatterns.wrapAsHolder(bottomRight), DyeColor.BLACK)
|
||||
)
|
||||
)
|
||||
);
|
||||
itemStack.set(DataComponents.HIDE_TOOLTIP, Unit.INSTANCE);
|
||||
addModelData(itemStack);
|
||||
return itemStack;
|
||||
|
||||
+7
-4
@@ -21,11 +21,13 @@ public class PlayerManager extends com.lishid.openinv.internal.paper1_21_4.playe
|
||||
protected @NotNull ServerPlayer createNewPlayer(
|
||||
@NotNull MinecraftServer server,
|
||||
@NotNull ServerLevel worldServer,
|
||||
@NotNull final OfflinePlayer offline) {
|
||||
@NotNull final OfflinePlayer offline
|
||||
) {
|
||||
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
|
||||
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
|
||||
GameProfile profile = new GameProfile(offline.getUniqueId(),
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()
|
||||
);
|
||||
|
||||
ClientInformation dummyInfo = new ClientInformation(
|
||||
"en_us",
|
||||
@@ -41,12 +43,13 @@ public class PlayerManager extends com.lishid.openinv.internal.paper1_21_4.playe
|
||||
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
|
||||
|
||||
try {
|
||||
injectPlayer(entity);
|
||||
injectPlayer(server, entity);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
}
|
||||
|
||||
return entity;
|
||||
|
||||
@@ -6,8 +6,8 @@ plugins {
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val paper = candidates.firstOrNull {
|
||||
it.id.let {
|
||||
id -> id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
}
|
||||
}
|
||||
if (paper != null) {
|
||||
@@ -21,6 +21,7 @@ dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
implementation(project(":openinvadaptercommon"))
|
||||
implementation(project(":openinvadapterpaper1_21_5"))
|
||||
implementation(project(":openinvadapterpaper1_21_4"))
|
||||
|
||||
paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT")
|
||||
|
||||
@@ -6,8 +6,8 @@ plugins {
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val paper = candidates.firstOrNull {
|
||||
it.id.let {
|
||||
id -> id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
}
|
||||
}
|
||||
if (paper != null) {
|
||||
@@ -21,6 +21,7 @@ dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
implementation(project(":openinvadaptercommon"))
|
||||
implementation(project(":openinvadapterpaper1_21_5"))
|
||||
|
||||
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
|
||||
}
|
||||
+7
-2
@@ -7,13 +7,18 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OpenEnderChest extends com.lishid.openinv.internal.common.container.OpenEnderChest{
|
||||
public class OpenEnderChest extends com.lishid.openinv.internal.common.container.OpenEnderChest {
|
||||
|
||||
public OpenEnderChest(@NotNull Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public @Nullable AbstractContainerMenu createMenu(net.minecraft.world.entity.player.Player player, int i, boolean viewOnly) {
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(
|
||||
net.minecraft.world.entity.player.Player player,
|
||||
int i,
|
||||
boolean viewOnly
|
||||
) {
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
return new OpenEnderChestMenu(this, serverPlayer, i, viewOnly);
|
||||
}
|
||||
|
||||
+28
-14
@@ -7,6 +7,7 @@ import com.lishid.openinv.internal.common.container.slot.ContentList;
|
||||
import com.lishid.openinv.internal.common.container.slot.ContentViewOnly;
|
||||
import com.lishid.openinv.internal.common.container.slot.SlotViewOnly;
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.Placeholders;
|
||||
import com.lishid.openinv.internal.paper1_21_4.container.bukkit.OpenPlayerInventory;
|
||||
import com.lishid.openinv.internal.paper1_21_4.container.menu.OpenInventoryMenu;
|
||||
import com.lishid.openinv.internal.paper1_21_4.container.slot.ContentEquipment;
|
||||
import com.lishid.openinv.internal.paper1_21_4.container.slot.ContentOffHand;
|
||||
@@ -79,12 +80,15 @@ public class OpenInventory extends com.lishid.openinv.internal.common.container.
|
||||
invIndex = localIndex - hotbarDiff;
|
||||
}
|
||||
|
||||
slots.set(localIndex, new ContentList(owner, invIndex, type) {
|
||||
@Override
|
||||
public void setHolder(@NotNull ServerPlayer holder) {
|
||||
items = holder.getInventory().items;
|
||||
}
|
||||
});
|
||||
slots.set(
|
||||
localIndex,
|
||||
new ContentList(owner, invIndex, type) {
|
||||
@Override
|
||||
public void setHolder(@NotNull ServerPlayer holder) {
|
||||
items = holder.getInventory().items;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return listSize;
|
||||
}
|
||||
@@ -151,22 +155,32 @@ public class OpenInventory extends com.lishid.openinv.internal.common.container.
|
||||
|
||||
if (pretty) {
|
||||
slots.set(startIndex + 2, new ContentViewOnly(owner) {
|
||||
@Override
|
||||
public Slot asSlot(Container container, int slot, int x, int y) {
|
||||
return new SlotViewOnly(container, slot, x, y) {
|
||||
@Override
|
||||
public ItemStack getOrDefault() {
|
||||
return Placeholders.craftingOutput;
|
||||
public Slot asSlot(Container container, int slot, int x, int y) {
|
||||
return new SlotViewOnly(container, slot, x, y) {
|
||||
@Override
|
||||
public ItemStack getOrDefault() {
|
||||
return Placeholders.craftingOutput;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
slots.set(startIndex + 11, getCraftingResult(owner));
|
||||
}
|
||||
|
||||
return startIndex + listSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull org.bukkit.inventory.Inventory getBukkitInventory() {
|
||||
if (bukkitEntity == null) {
|
||||
bukkitEntity = new OpenPlayerInventory(this);
|
||||
}
|
||||
return bukkitEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(Player player, int i, boolean viewOnly) {
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
return new OpenInventoryMenu(this, serverPlayer, i, viewOnly);
|
||||
|
||||
+2
@@ -170,12 +170,14 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
|
||||
getInventory().getOwnerHandle().getInventory().offhand.set(0, CraftItemStack.asNMSCopy(item));
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setItemInHand(@Nullable ItemStack stack) {
|
||||
|
||||
+5
-2
@@ -35,6 +35,7 @@ import java.util.function.Supplier;
|
||||
/**
|
||||
* An extension of {@link AbstractContainerMenu} that supports {@link SlotPlaceholder placeholders}.
|
||||
*/
|
||||
@SuppressWarnings("HidingField")
|
||||
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
|
||||
extends AbstractContainerMenu {
|
||||
|
||||
@@ -59,7 +60,8 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
|
||||
int containerCounter,
|
||||
@NotNull T container,
|
||||
@NotNull ServerPlayer viewer,
|
||||
boolean viewOnly) {
|
||||
boolean viewOnly
|
||||
) {
|
||||
super(type, containerCounter);
|
||||
this.container = container;
|
||||
this.viewer = viewer;
|
||||
@@ -108,7 +110,8 @@ public abstract class OpenChestMenu<T extends Container & ISpecialInventory & In
|
||||
}
|
||||
}
|
||||
|
||||
protected void preSlotSetup() {}
|
||||
protected void preSlotSetup() {
|
||||
}
|
||||
|
||||
protected @NotNull Slot getUpperSlot(int index, int x, int y) {
|
||||
Slot slot = new Slot(container, index, x, y);
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@ public class OpenEnderChestMenu extends OpenChestMenu<OpenEnderChest> {
|
||||
@NotNull OpenEnderChest enderChest,
|
||||
@NotNull ServerPlayer viewer,
|
||||
int containerId,
|
||||
boolean viewOnly) {
|
||||
boolean viewOnly
|
||||
) {
|
||||
super(
|
||||
com.lishid.openinv.internal.common.container.menu.OpenChestMenu.getChestMenuType(enderChest.getContainerSize()),
|
||||
containerId,
|
||||
|
||||
+5
-2
@@ -133,8 +133,11 @@ public class OpenInventoryMenu extends OpenChestMenu<OpenInventory> {
|
||||
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
|
||||
return null;
|
||||
}
|
||||
Preconditions.checkArgument(rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
|
||||
"Slot %s outside of inventory", rawSlot);
|
||||
Preconditions.checkArgument(
|
||||
rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
|
||||
"Slot %s outside of inventory",
|
||||
rawSlot
|
||||
);
|
||||
if (rawSlot > topSize) {
|
||||
return getBottomInventory();
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
package com.lishid.openinv.internal.paper1_21_4.container.slot;
|
||||
|
||||
import com.lishid.openinv.internal.common.player.OpenPlayer;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
@@ -37,13 +37,14 @@ public class ContentOffHand extends ContentEquipment {
|
||||
return new SlotEquipment(container, slot, x, y) {
|
||||
@Override
|
||||
public void setChanged() {
|
||||
if (OpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
|
||||
if (BaseOpenPlayer.isConnected(holder.connection) && holder.containerMenu != holder.inventoryMenu) {
|
||||
holder.connection.send(
|
||||
new ClientboundContainerSetSlotPacket(
|
||||
holder.inventoryMenu.containerId,
|
||||
holder.inventoryMenu.incrementStateId(),
|
||||
InventoryMenu.SHIELD_SLOT,
|
||||
holder.getOffhandItem()));
|
||||
holder.getOffhandItem()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+5
-4
@@ -1,6 +1,5 @@
|
||||
package com.lishid.openinv.internal.paper1_21_4.player;
|
||||
|
||||
import com.lishid.openinv.internal.common.player.PlayerManager;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
@@ -8,10 +7,12 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OpenPlayer extends com.lishid.openinv.internal.common.player.OpenPlayer {
|
||||
public class OpenPlayer extends com.lishid.openinv.internal.paper1_21_5.player.OpenPlayer {
|
||||
|
||||
protected OpenPlayer(CraftServer server, ServerPlayer entity,
|
||||
PlayerManager manager) {
|
||||
protected OpenPlayer(
|
||||
CraftServer server, ServerPlayer entity,
|
||||
PlayerManager manager
|
||||
) {
|
||||
super(server, entity, manager);
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -3,6 +3,7 @@ package com.lishid.openinv.internal.paper1_21_4.player;
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -15,12 +16,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class PlayerManager extends com.lishid.openinv.internal.common.player.PlayerManager {
|
||||
public class PlayerManager extends com.lishid.openinv.internal.paper1_21_5.player.PlayerManager {
|
||||
|
||||
public PlayerManager(@NotNull Logger logger) {
|
||||
super(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
|
||||
// See PlayerList#placeNewPlayer
|
||||
World bukkitWorld;
|
||||
@@ -36,19 +38,19 @@ public class PlayerManager extends com.lishid.openinv.internal.common.player.Pla
|
||||
.resultOrPartial(logger::warning)
|
||||
.map(player.server::getLevel)
|
||||
// If ServerLevel exists, set, otherwise move to spawn.
|
||||
.ifPresentOrElse(player::setServerLevel, () -> spawnInDefaultWorld(player));
|
||||
.ifPresentOrElse(player::setServerLevel, () -> spawnInDefaultWorld(player.server, player));
|
||||
return;
|
||||
}
|
||||
if (bukkitWorld == null) {
|
||||
spawnInDefaultWorld(player);
|
||||
spawnInDefaultWorld(player.server, player);
|
||||
return;
|
||||
}
|
||||
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnInDefaultWorld(ServerPlayer player) {
|
||||
ServerLevel level = player.server.getLevel(Level.OVERWORLD);
|
||||
protected void spawnInDefaultWorld(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
|
||||
ServerLevel level = server.getLevel(Level.OVERWORLD);
|
||||
if (level != null) {
|
||||
// Adjust player to default spawn (in keeping with Paper handling) when world not found.
|
||||
player.moveTo(player.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), level.getSharedSpawnAngle(), 0.0F);
|
||||
@@ -58,7 +60,8 @@ public class PlayerManager extends com.lishid.openinv.internal.common.player.Pla
|
||||
}
|
||||
}
|
||||
|
||||
protected void injectPlayer(ServerPlayer player) throws IllegalAccessException {
|
||||
@Override
|
||||
protected void injectPlayer(@NotNull MinecraftServer server, @NotNull ServerPlayer player) throws IllegalAccessException {
|
||||
if (bukkitEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
`openinv-base`
|
||||
alias(libs.plugins.paperweight)
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val paper = candidates.firstOrNull {
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "paper-api"
|
||||
}
|
||||
}
|
||||
if (paper != null) {
|
||||
select(paper)
|
||||
}
|
||||
because("module is written for Paper servers")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
implementation(project(":openinvadaptercommon"))
|
||||
|
||||
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT")
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.lishid.openinv.internal.paper1_21_5;
|
||||
|
||||
import com.lishid.openinv.internal.paper1_21_5.container.slot.placeholder.PlaceholderLoaderLegacyParse;
|
||||
import com.lishid.openinv.internal.paper1_21_5.player.PlayerManager;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class InternalAccessor extends com.lishid.openinv.internal.common.InternalAccessor {
|
||||
|
||||
private final @NotNull PlayerManager manager;
|
||||
|
||||
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
super(logger, lang);
|
||||
manager = new PlayerManager(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PlayerManager getPlayerManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(@NotNull ConfigurationSection config) {
|
||||
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
|
||||
try {
|
||||
// Reset placeholders to defaults and try to load configuration.
|
||||
new PlaceholderLoaderLegacyParse().load(placeholders);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.lishid.openinv.internal.paper1_21_5.container.slot.placeholder;
|
||||
|
||||
import com.lishid.openinv.internal.common.container.slot.placeholder.PlaceholderLoader;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class PlaceholderLoaderLegacyParse extends PlaceholderLoader {
|
||||
|
||||
@Override
|
||||
protected @NotNull ItemStack parse(
|
||||
@Nullable ConfigurationSection section,
|
||||
@NotNull String path,
|
||||
@NotNull ItemStack defaultStack
|
||||
) throws Exception {
|
||||
if (section == null) {
|
||||
return defaultStack;
|
||||
}
|
||||
|
||||
String itemText = section.getString(path);
|
||||
|
||||
if (itemText == null) {
|
||||
return defaultStack;
|
||||
}
|
||||
|
||||
CompoundTag compoundTag = parseTag(itemText);
|
||||
Optional<ItemStack> parsed = ItemStack.parse(CraftRegistry.getMinecraftRegistry(), compoundTag);
|
||||
return parsed.filter(itemStack -> !itemStack.isEmpty()).orElse(defaultStack);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.lishid.openinv.internal.paper1_21_5.player;
|
||||
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
import com.lishid.openinv.internal.common.player.BaseOpenPlayer;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.storage.PlayerDataStorage;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class OpenPlayer extends BaseOpenPlayer {
|
||||
|
||||
protected OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
|
||||
super(server, entity, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData() {
|
||||
if (OpenEvents.saveCancelled(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer player = this.getHandle();
|
||||
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
|
||||
try {
|
||||
PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo;
|
||||
|
||||
CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player.getName().getString(), player.getStringUUID()).orElse(null);
|
||||
CompoundTag playerData = getWritableTag(oldData);
|
||||
|
||||
playerData = player.saveWithoutId(playerData);
|
||||
|
||||
if (oldData != null) {
|
||||
// Revert certain special data values when offline.
|
||||
revertSpecialValues(playerData, oldData);
|
||||
}
|
||||
|
||||
Path playerDataDir = worldNBTStorage.getPlayerDir().toPath();
|
||||
Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat");
|
||||
NbtIo.writeCompressed(playerData, tempFile);
|
||||
Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat");
|
||||
Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old");
|
||||
Util.safeReplaceFile(dataFile, tempFile, backupFile);
|
||||
} catch (Exception e) {
|
||||
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.lishid.openinv.internal.paper1_21_5.player;
|
||||
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class PlayerManager extends com.lishid.openinv.internal.common.player.PlayerManager {
|
||||
|
||||
public PlayerManager(@NotNull Logger logger) {
|
||||
super(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean loadData(@NotNull MinecraftServer server, @NotNull ServerPlayer player) {
|
||||
// See CraftPlayer#loadData
|
||||
CompoundTag loadedData = server.getPlayerList().playerIo.load(player).orElse(null);
|
||||
|
||||
if (loadedData == null) {
|
||||
// Exceptions with loading are logged.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read basic data into the player.
|
||||
player.load(loadedData);
|
||||
// Game type settings are also loaded separately.
|
||||
player.loadGameTypes(loadedData);
|
||||
|
||||
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
|
||||
parseWorld(player, loadedData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
|
||||
// See PlayerList#placeNewPlayer
|
||||
World bukkitWorld;
|
||||
Optional<Long> msbs = loadedData.getLong("WorldUUIDMost");
|
||||
Optional<Long> lsbs = loadedData.getLong("WorldUUIDLeast");
|
||||
if (msbs.isPresent() && lsbs.isPresent()) {
|
||||
// Modern Bukkit world.
|
||||
bukkitWorld = Bukkit.getServer().getWorld(new UUID(msbs.get(), lsbs.get()));
|
||||
} else {
|
||||
Optional<String> worldName = loadedData.getString("world");
|
||||
if (worldName.isPresent()) {
|
||||
// Legacy Bukkit world.
|
||||
bukkitWorld = Bukkit.getServer().getWorld(worldName.get());
|
||||
} else {
|
||||
// Vanilla player data.
|
||||
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
|
||||
.resultOrPartial(logger::warning)
|
||||
.map(player.server::getLevel)
|
||||
// If ServerLevel exists, set, otherwise move to spawn.
|
||||
.ifPresentOrElse(player::setServerLevel, () -> spawnInDefaultWorld(player.server, player));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bukkitWorld == null) {
|
||||
spawnInDefaultWorld(player.server, player);
|
||||
return;
|
||||
}
|
||||
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player inject(@NotNull Player player) {
|
||||
try {
|
||||
ServerPlayer nmsPlayer = getHandle(player);
|
||||
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
|
||||
return openPlayer;
|
||||
}
|
||||
injectPlayer(nmsPlayer.server, nmsPlayer);
|
||||
return nmsPlayer.getBukkitEntity();
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void injectPlayer(@NotNull MinecraftServer server, @NotNull ServerPlayer player) throws IllegalAccessException {
|
||||
if (bukkitEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
bukkitEntity.setAccessible(true);
|
||||
|
||||
bukkitEntity.set(player, new OpenPlayer(player.server.server, player, this));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,15 +10,15 @@ plugins {
|
||||
apply<SpigotSetup>()
|
||||
apply<SpigotReobf>()
|
||||
|
||||
val spigotVer = "1.21.5-R0.1-SNAPSHOT"
|
||||
val spigotVer = "1.21.8-R0.1-SNAPSHOT"
|
||||
// Used by common adapter to relocate Craftbukkit classes to a versioned package.
|
||||
rootProject.extra["craftbukkitPackage"] = "v1_21_R4"
|
||||
rootProject.extra["craftbukkitPackage"] = "v1_21_R5"
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.spigotmc:spigot-api") {
|
||||
val spigot = candidates.firstOrNull {
|
||||
it.id.let {
|
||||
id -> id is ModuleComponentIdentifier && id.module == "spigot-api"
|
||||
it.id.let { id ->
|
||||
id is ModuleComponentIdentifier && id.module == "spigot-api"
|
||||
}
|
||||
}
|
||||
if (spigot != null) {
|
||||
|
||||
+12
-6
@@ -64,7 +64,8 @@ public class AnySilentContainer extends AnySilentContainerBase {
|
||||
public boolean activateContainer(
|
||||
@NotNull final Player bukkitPlayer,
|
||||
final boolean silentchest,
|
||||
@NotNull final org.bukkit.block.Block bukkitBlock) {
|
||||
@NotNull final org.bukkit.block.Block bukkitBlock
|
||||
) {
|
||||
|
||||
// Silent ender chest is API-only
|
||||
if (silentchest && bukkitBlock.getType() == Material.ENDER_CHEST) {
|
||||
@@ -87,11 +88,16 @@ public class AnySilentContainer extends AnySilentContainerBase {
|
||||
// Anychest ender chest. See net.minecraft.world.level.block.EnderChestBlock
|
||||
PlayerEnderChestContainer enderChest = player.getEnderChestInventory();
|
||||
enderChest.setActiveChest(enderChestTile);
|
||||
player.openMenu(new SimpleMenuProvider((containerCounter, playerInventory, ignored) -> {
|
||||
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
|
||||
int rows = enderChest.getContainerSize() / 9;
|
||||
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
|
||||
}, Component.translatable("container.enderchest")));
|
||||
player.openMenu(
|
||||
new SimpleMenuProvider(
|
||||
(containerCounter, playerInventory, ignored) -> {
|
||||
MenuType<?> containers = OpenChestMenu.getChestMenuType(enderChest.getContainerSize());
|
||||
int rows = enderChest.getContainerSize() / 9;
|
||||
return new ChestMenu(containers, containerCounter, playerInventory, enderChest, rows);
|
||||
},
|
||||
Component.translatable("container.enderchest")
|
||||
)
|
||||
);
|
||||
bukkitPlayer.incrementStatistic(Statistic.ENDERCHEST_OPENED);
|
||||
return true;
|
||||
}
|
||||
|
||||
+4
-2
@@ -4,8 +4,8 @@ import com.google.common.base.Preconditions;
|
||||
import com.lishid.openinv.internal.reobf.container.OpenInventory;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftInventory;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftInventory;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -151,12 +151,14 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
|
||||
getInventory().getOwnerHandle().getBukkitEntity().getInventory().setItemInOffHand(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@SuppressWarnings("InlineMeSuggester")
|
||||
@Deprecated
|
||||
@Override
|
||||
public void setItemInHand(@Nullable ItemStack stack) {
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.CraftEquipmentSlot;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftEquipmentSlot;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
+2
-1
@@ -40,7 +40,8 @@ public class ContentOffHand extends ContentEquipment {
|
||||
holder.inventoryMenu.containerId,
|
||||
holder.inventoryMenu.incrementStateId(),
|
||||
InventoryMenu.SHIELD_SLOT,
|
||||
holder.getOffhandItem()));
|
||||
holder.getOffhandItem()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
package com.lishid.openinv.internal.reobf.player;
|
||||
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.NumericTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.level.storage.PlayerDataStorage;
|
||||
import net.minecraft.world.level.storage.TagValueInput;
|
||||
import net.minecraft.world.level.storage.TagValueOutput;
|
||||
import net.minecraft.world.level.storage.ValueInput;
|
||||
import net.minecraft.world.level.storage.ValueOutput;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenPlayer extends CraftPlayer {
|
||||
|
||||
/**
|
||||
* List of tags to always reset when saving.
|
||||
*
|
||||
* @see net.minecraft.world.entity.Entity#saveWithoutId(ValueOutput)
|
||||
* @see ServerPlayer#addAdditionalSaveData(ValueOutput)
|
||||
* @see net.minecraft.world.entity.player.Player#addAdditionalSaveData(ValueOutput)
|
||||
* @see net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(ValueOutput)
|
||||
*/
|
||||
@Unmodifiable
|
||||
protected static final Set<String> RESET_TAGS = Set.of(
|
||||
// Entity#saveWithoutId(CompoundTag)
|
||||
"CustomName",
|
||||
"CustomNameVisible",
|
||||
"Silent",
|
||||
"NoGravity",
|
||||
"Glowing",
|
||||
"TicksFrozen",
|
||||
"HasVisualFire",
|
||||
"Tags",
|
||||
"Passengers",
|
||||
// ServerPlayer#addAdditionalSaveData(CompoundTag)
|
||||
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
|
||||
"warden_spawn_tracker",
|
||||
"entered_nether_pos", // Replaces enteredNetherPosition
|
||||
"enteredNetherPosition",
|
||||
"respawn", // Replaces SpawnXyz fields as of 1.21.6
|
||||
"SpawnX",
|
||||
"SpawnY",
|
||||
"SpawnZ",
|
||||
"SpawnForced",
|
||||
"SpawnAngle",
|
||||
"SpawnDimension",
|
||||
"raid_omen_position",
|
||||
"ender_pearls",
|
||||
// Player#addAdditionalSaveData(CompoundTag)
|
||||
"ShoulderEntityLeft",
|
||||
"ShoulderEntityRight",
|
||||
"LastDeathLocation",
|
||||
"current_explosion_impact_pos",
|
||||
// LivingEntity#addAdditionalSaveData(CompoundTag)
|
||||
"active_effects",
|
||||
"sleeping_pos", // Replaces SleepingXyz fields as of 1.21.6
|
||||
"SleepingX",
|
||||
"SleepingY",
|
||||
"SleepingZ",
|
||||
"Brain",
|
||||
"last_hurt_by_player",
|
||||
"last_hurt_by_player_memory_time",
|
||||
"last_hurt_by_mob",
|
||||
"ticks_since_last_hurt_by_mob",
|
||||
"equipment",
|
||||
"locator_bar_icon"
|
||||
);
|
||||
|
||||
private final PlayerManager manager;
|
||||
|
||||
protected OpenPlayer(CraftServer server, ServerPlayer entity, PlayerManager manager) {
|
||||
super(server, entity);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData() {
|
||||
manager.loadData(getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData() {
|
||||
if (OpenEvents.saveCancelled(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer player = this.getHandle();
|
||||
Logger logger = LogUtils.getLogger();
|
||||
// See net.minecraft.world.level.storage.PlayerDataStorage#save(EntityHuman)
|
||||
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), logger)) {
|
||||
PlayerDataStorage worldNBTStorage = server.getServer().getPlayerList().playerIo;
|
||||
|
||||
ValueInput oldDataInput = isOnline()
|
||||
? null
|
||||
: worldNBTStorage.load(player.getName().getString(), player.getStringUUID(), scopedCollector, player.registryAccess()).orElse(null);
|
||||
// Grab CompoundTag out of TagValueInput - we can't iterate over the fields via TagValueInput.
|
||||
CompoundTag oldData = null;
|
||||
if (oldDataInput != null) {
|
||||
Field tagValueInputInput = TagValueInput.class.getDeclaredField("input");
|
||||
tagValueInputInput.setAccessible(true);
|
||||
oldData = (CompoundTag) tagValueInputInput.get(oldDataInput);
|
||||
}
|
||||
|
||||
CompoundTag playerData = getWritableTag(oldData);
|
||||
|
||||
ValueOutput valueOutput = TagValueOutput.createWithContext(scopedCollector, player.registryAccess());
|
||||
Field tagValueOutputOutput = TagValueOutput.class.getDeclaredField("output");
|
||||
tagValueOutputOutput.setAccessible(true);
|
||||
CompoundTag newPlayerData = (CompoundTag) tagValueOutputOutput.get(valueOutput);
|
||||
// Add existing old data.
|
||||
newPlayerData.merge(playerData);
|
||||
playerData = newPlayerData;
|
||||
|
||||
player.saveWithoutId(valueOutput);
|
||||
|
||||
if (oldData != null) {
|
||||
// Revert certain special data values when offline.
|
||||
revertSpecialValues(playerData, oldData);
|
||||
}
|
||||
|
||||
Path playerDataDir = worldNBTStorage.getPlayerDir().toPath();
|
||||
Path tempFile = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat");
|
||||
NbtIo.writeCompressed(playerData, tempFile);
|
||||
Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat");
|
||||
Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old");
|
||||
Util.safeReplaceFile(dataFile, tempFile, backupFile);
|
||||
} catch (Exception e) {
|
||||
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null -> new")
|
||||
protected @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) {
|
||||
if (oldData == null) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
|
||||
// Copy old data. This is a deep clone, so operating on it should be safe.
|
||||
oldData = oldData.copy();
|
||||
|
||||
// Remove vanilla/server data that is not written every time.
|
||||
oldData.keySet().removeIf(
|
||||
key -> RESET_TAGS.contains(key)
|
||||
|| key.startsWith("Bukkit")
|
||||
|| (key.startsWith("Paper") && key.length() > 5)
|
||||
);
|
||||
|
||||
return oldData;
|
||||
}
|
||||
|
||||
protected void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) {
|
||||
// Revert automatic updates to play timestamps.
|
||||
copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class);
|
||||
copyValue(oldData, newData, "Paper", "LastLogin", NumericTag.class);
|
||||
}
|
||||
|
||||
private <T extends Tag> void copyValue(
|
||||
@NotNull CompoundTag source,
|
||||
@NotNull CompoundTag target,
|
||||
@NotNull String container,
|
||||
@NotNull String key,
|
||||
@SuppressWarnings("SameParameterValue") @NotNull Class<T> tagType
|
||||
) {
|
||||
CompoundTag oldContainer = getTag(source, container, CompoundTag.class);
|
||||
CompoundTag newContainer = getTag(target, container, CompoundTag.class);
|
||||
|
||||
// New container being null means the server implementation doesn't store this data.
|
||||
if (newContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If old tag exists, copy it to new location, removing otherwise.
|
||||
setTag(newContainer, key, getTag(oldContainer, key, tagType));
|
||||
}
|
||||
|
||||
private <T extends Tag> @Nullable T getTag(
|
||||
@Nullable CompoundTag container,
|
||||
@NotNull String key,
|
||||
@NotNull Class<T> dataType
|
||||
) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
Tag value = container.get(key);
|
||||
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
|
||||
return null;
|
||||
}
|
||||
return dataType.cast(value);
|
||||
}
|
||||
|
||||
private <T extends Tag> void setTag(
|
||||
@NotNull CompoundTag container,
|
||||
@NotNull String key,
|
||||
@Nullable T data
|
||||
) {
|
||||
if (data == null) {
|
||||
container.remove(key);
|
||||
} else {
|
||||
container.put(key, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isConnected(@Nullable ServerGamePacketListenerImpl connection) {
|
||||
return connection != null && !connection.isDisconnected();
|
||||
}
|
||||
|
||||
}
|
||||
+39
-25
@@ -3,8 +3,8 @@ package com.lishid.openinv.internal.reobf.player;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.internal.reobf.container.OpenEnderChest;
|
||||
import com.lishid.openinv.internal.reobf.container.OpenInventory;
|
||||
import com.lishid.openinv.util.JulLoggerAdapter;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -12,16 +12,18 @@ import net.minecraft.server.level.ClientInformation;
|
||||
import net.minecraft.server.level.ParticleStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.ChatVisiblity;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ValueInput;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.event.CraftEventFactory;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.event.CraftEventFactory;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -47,15 +49,15 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
}
|
||||
|
||||
public static @NotNull ServerPlayer getHandle(final Player player) {
|
||||
if (player instanceof CraftPlayer) {
|
||||
return ((CraftPlayer) player).getHandle();
|
||||
if (player instanceof CraftPlayer craftPlayer) {
|
||||
return craftPlayer.getHandle();
|
||||
}
|
||||
|
||||
Server server = player.getServer();
|
||||
ServerPlayer nmsPlayer = null;
|
||||
|
||||
if (server instanceof CraftServer) {
|
||||
nmsPlayer = ((CraftServer) server).getHandle().getPlayer(player.getUniqueId());
|
||||
if (server instanceof CraftServer craftServer) {
|
||||
nmsPlayer = craftServer.getHandle().getPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
if (nmsPlayer == null) {
|
||||
@@ -68,7 +70,11 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
|
||||
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftServer server = craftServer.getServer();
|
||||
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
|
||||
|
||||
if (worldServer == null) {
|
||||
@@ -93,11 +99,13 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
private @NotNull ServerPlayer createNewPlayer(
|
||||
@NotNull MinecraftServer server,
|
||||
@NotNull ServerLevel worldServer,
|
||||
@NotNull final OfflinePlayer offline) {
|
||||
@NotNull final OfflinePlayer offline
|
||||
) {
|
||||
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
|
||||
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
|
||||
GameProfile profile = new GameProfile(offline.getUniqueId(),
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
|
||||
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()
|
||||
);
|
||||
|
||||
ClientInformation dummyInfo = new ClientInformation(
|
||||
"en_us",
|
||||
@@ -119,7 +127,8 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
}
|
||||
|
||||
return entity;
|
||||
@@ -127,20 +136,21 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
|
||||
boolean loadData(@NotNull ServerPlayer player) {
|
||||
// See CraftPlayer#loadData
|
||||
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player).orElse(null);
|
||||
|
||||
if (loadedData == null) {
|
||||
// Exceptions with loading are logged by Mojang.
|
||||
return false;
|
||||
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
|
||||
ValueInput loadedData = player.server.getPlayerList().playerIo.load(player, scopedCollector).orElse(null);
|
||||
|
||||
if (loadedData == null) {
|
||||
// Exceptions with loading are logged.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read basic data into the player.
|
||||
player.load(loadedData);
|
||||
// Game type settings are loaded separately.
|
||||
player.loadGameTypes(loadedData);
|
||||
}
|
||||
|
||||
// Read basic data into the player.
|
||||
player.load(loadedData);
|
||||
// Also read "extra" data.
|
||||
player.readAdditionalSaveData(loadedData);
|
||||
// Game type settings are also loaded separately.
|
||||
player.loadGameTypes(loadedData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,13 +177,17 @@ public class PlayerManager implements com.lishid.openinv.internal.PlayerManager
|
||||
logger.log(
|
||||
java.util.logging.Level.WARNING,
|
||||
e,
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
|
||||
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!"
|
||||
);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
public @Nullable InventoryView openInventory(
|
||||
@NotNull Player bukkitPlayer, @NotNull ISpecialInventory inventory,
|
||||
boolean viewOnly
|
||||
) {
|
||||
ServerPlayer player = getHandle(bukkitPlayer);
|
||||
|
||||
if (!OpenPlayer.isConnected(player.connection)) {
|
||||
|
||||
@@ -13,6 +13,7 @@ dependencies {
|
||||
implementation(project(":openinvapi"))
|
||||
implementation(project(":openinvcommon"))
|
||||
implementation(project(":openinvadaptercommon"))
|
||||
implementation(project(":openinvadapterpaper1_21_5"))
|
||||
implementation(project(":openinvadapterpaper1_21_4"))
|
||||
implementation(project(":openinvadapterpaper1_21_3"))
|
||||
implementation(project(":openinvadapterpaper1_21_1"))
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.lishid.openinv;
|
||||
|
||||
import com.lishid.openinv.command.ClearInvCommand;
|
||||
import com.lishid.openinv.command.ContainerSettingCommand;
|
||||
import com.lishid.openinv.command.OpenInvCommand;
|
||||
import com.lishid.openinv.command.SearchContainerCommand;
|
||||
@@ -48,7 +49,6 @@ import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -62,243 +62,249 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public class OpenInv extends FoliaWrappedJavaPlugin implements IOpenInv {
|
||||
|
||||
private InternalAccessor accessor;
|
||||
private Config config;
|
||||
private InventoryManager inventoryManager;
|
||||
private LanguageManager languageManager;
|
||||
private PlayerLoader playerLoader;
|
||||
private boolean isSpigot = false;
|
||||
private InternalAccessor accessor;
|
||||
private Config config;
|
||||
private InventoryManager inventoryManager;
|
||||
private LanguageManager languageManager;
|
||||
private PlayerLoader playerLoader;
|
||||
private boolean isSpigot = false;
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
config.reload(getConfig());
|
||||
languageManager.reload();
|
||||
if (accessor != null && accessor.isSupported()) {
|
||||
accessor.reload(getConfig());
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
config.reload(getConfig());
|
||||
languageManager.reload();
|
||||
if (accessor != null && accessor.isSupported()) {
|
||||
accessor.reload(getConfig());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!isSpigot || !this.accessor.isSupported()) {
|
||||
this.sendVersionError(sender::sendMessage);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
inventoryManager.evictAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Save default configuration if not present.
|
||||
this.saveDefaultConfig();
|
||||
|
||||
// Migrate locale files to a subfolder.
|
||||
Path dataFolder = getDataFolder().toPath();
|
||||
new LangMigrator(dataFolder, dataFolder.resolve("locale"), getLogger()).migrate();
|
||||
|
||||
// Set up configurable features. Note that #reloadConfig is called on the first call to #getConfig!
|
||||
// Configuration values should not be accessed until after all of these have been set up.
|
||||
config = new Config();
|
||||
languageManager = new LanguageManager(this, "en");
|
||||
accessor = new InternalAccessor(getLogger(), languageManager);
|
||||
|
||||
// Perform initial config load.
|
||||
reloadConfig();
|
||||
|
||||
inventoryManager = new InventoryManager(this, config, accessor);
|
||||
playerLoader = new PlayerLoader(this, config, inventoryManager, accessor, getLogger());
|
||||
|
||||
try {
|
||||
Class.forName("org.bukkit.entity.Player$Spigot");
|
||||
isSpigot = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
isSpigot = false;
|
||||
}
|
||||
|
||||
// Version check
|
||||
if (isSpigot && this.accessor.isSupported()) {
|
||||
reloadConfig();
|
||||
|
||||
// Update existing configuration. May require internal access.
|
||||
new ConfigUpdater(this).checkForUpdates();
|
||||
|
||||
// Register relevant event listeners.
|
||||
registerEvents();
|
||||
|
||||
// Register commands to their executors.
|
||||
registerCommands();
|
||||
|
||||
} else {
|
||||
this.sendVersionError(this.getLogger()::warning);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void registerEvents() {
|
||||
PluginManager pluginManager = this.getServer().getPluginManager();
|
||||
pluginManager.registerEvents(playerLoader, this);
|
||||
pluginManager.registerEvents(inventoryManager, this);
|
||||
pluginManager.registerEvents(new ContainerListener(accessor, languageManager), this);
|
||||
pluginManager.registerEvents(new ToggleListener(), this);
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
this.setCommandExecutor(new OpenInvCommand(this, config, inventoryManager, languageManager, playerLoader), "openinv", "openender");
|
||||
this.setCommandExecutor(new SearchContainerCommand(this, languageManager), "searchcontainer");
|
||||
this.setCommandExecutor(new SearchInvCommand(languageManager), "searchinv", "searchender");
|
||||
this.setCommandExecutor(new SearchEnchantCommand(languageManager), "searchenchant");
|
||||
this.setCommandExecutor(new ClearInvCommand(this, config, inventoryManager, languageManager, playerLoader), "clearinv", "clearender");
|
||||
|
||||
ContainerSettingCommand settingCommand = new ContainerSettingCommand(languageManager);
|
||||
for (PlayerToggle toggle : PlayerToggles.get()) {
|
||||
setCommandExecutor(settingCommand, toggle.getName().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
}
|
||||
|
||||
private void setCommandExecutor(@NotNull CommandExecutor executor, String @NotNull ... commands) {
|
||||
for (String commandName : commands) {
|
||||
PluginCommand command = this.getCommand(commandName);
|
||||
if (command != null) {
|
||||
command.setExecutor(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendVersionError(@NotNull Consumer<String> messageMethod) {
|
||||
if (!accessor.isSupported()) {
|
||||
messageMethod.accept("Your server version (" + accessor.getVersion() + ") is not supported.");
|
||||
messageMethod.accept("Please download the correct version of OpenInv here: " + accessor.getReleasesLink());
|
||||
}
|
||||
if (!isSpigot) {
|
||||
messageMethod.accept("OpenInv requires that you use Spigot or a Spigot fork. Per the 1.14 update thread");
|
||||
messageMethod.accept("(https://www.spigotmc.org/threads/369724/ \"A Note on CraftBukkit\"), if you are");
|
||||
messageMethod.accept("encountering an inconsistency with vanilla that prevents you from using Spigot,");
|
||||
messageMethod.accept("that is considered a Spigot bug and should be reported as such.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedVersion() {
|
||||
return this.accessor != null && this.accessor.isSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableSaving() {
|
||||
return config.isSaveDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableOfflineAccess() {
|
||||
return config.isOfflineDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean noArgsOpensSelf() {
|
||||
return config.doesNoArgsOpenSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
return this.accessor.getAnySilentContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAnyContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return PlayerToggles.any().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnyContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
PlayerToggles.any().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSilentContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return PlayerToggles.silent().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSilentContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
PlayerToggles.silent().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online) {
|
||||
return inventoryManager.getEnderChest(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online) {
|
||||
return inventoryManager.getInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
Permissions edit = null;
|
||||
HumanEntity target = inventory.getPlayer();
|
||||
boolean ownContainer = player.equals(target);
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER;
|
||||
} else if (inventory instanceof ISpecialEnderChest) {
|
||||
edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_EDIT_OTHER;
|
||||
}
|
||||
|
||||
boolean viewOnly = edit != null && !edit.hasPermission(player);
|
||||
|
||||
if (ownContainer || (viewOnly && config.getAccessEqualMode() != AccessEqualMode.DENY)) {
|
||||
return this.accessor.openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
// If the target doesn't have this access level...
|
||||
if (!target.hasPermission(permission)) {
|
||||
// If the viewer does have the access level, all good.
|
||||
if (player.hasPermission(permission)) {
|
||||
break;
|
||||
}
|
||||
// Otherwise check next access level.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the viewer doesn't have an equal access level or equal access is a denial, deny.
|
||||
if (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since this is a tie, setting decides view state.
|
||||
if (config.getAccessEqualMode() == AccessEqualMode.VIEW) {
|
||||
viewOnly = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!isSpigot || !this.accessor.isSupported()) {
|
||||
this.sendVersionError(sender::sendMessage);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.accessor.openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
inventoryManager.evictAll();
|
||||
}
|
||||
@Override
|
||||
public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
|
||||
return inventoryManager.getLoadedPlayer(playerUuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Save default configuration if not present.
|
||||
this.saveDefaultConfig();
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
return playerLoader.load(offline);
|
||||
}
|
||||
|
||||
// Migrate locale files to a subfolder.
|
||||
Path dataFolder = getDataFolder().toPath();
|
||||
new LangMigrator(dataFolder, dataFolder.resolve("locale"), getLogger()).migrate();
|
||||
@Override
|
||||
public @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
|
||||
return playerLoader.match(name);
|
||||
}
|
||||
|
||||
// Set up configurable features. Note that #reloadConfig is called on the first call to #getConfig!
|
||||
// Configuration values should not be accessed until after all of these have been set up.
|
||||
config = new Config();
|
||||
languageManager = new LanguageManager(this, "en");
|
||||
accessor = new InternalAccessor(getLogger(), languageManager);
|
||||
|
||||
// Perform initial config load.
|
||||
reloadConfig();
|
||||
|
||||
inventoryManager = new InventoryManager(this, config, accessor);
|
||||
playerLoader = new PlayerLoader(this, config, inventoryManager, accessor, getLogger());
|
||||
|
||||
try {
|
||||
Class.forName("org.bukkit.entity.Player$Spigot");
|
||||
isSpigot = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
isSpigot = false;
|
||||
}
|
||||
|
||||
// Version check
|
||||
if (isSpigot && this.accessor.isSupported()) {
|
||||
reloadConfig();
|
||||
|
||||
// Update existing configuration. May require internal access.
|
||||
new ConfigUpdater(this).checkForUpdates();
|
||||
|
||||
// Register relevant event listeners.
|
||||
registerEvents();
|
||||
|
||||
// Register commands to their executors.
|
||||
registerCommands();
|
||||
|
||||
} else {
|
||||
this.sendVersionError(this.getLogger()::warning);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void registerEvents() {
|
||||
PluginManager pluginManager = this.getServer().getPluginManager();
|
||||
pluginManager.registerEvents(playerLoader, this);
|
||||
pluginManager.registerEvents(inventoryManager, this);
|
||||
pluginManager.registerEvents(new ContainerListener(accessor, languageManager), this);
|
||||
pluginManager.registerEvents(new ToggleListener(), this);
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
this.setCommandExecutor(new OpenInvCommand(this, config, inventoryManager, languageManager, playerLoader), "openinv", "openender");
|
||||
this.setCommandExecutor(new SearchContainerCommand(this, languageManager), "searchcontainer");
|
||||
this.setCommandExecutor(new SearchInvCommand(languageManager), "searchinv", "searchender");
|
||||
this.setCommandExecutor(new SearchEnchantCommand(languageManager), "searchenchant");
|
||||
|
||||
ContainerSettingCommand settingCommand = new ContainerSettingCommand(languageManager);
|
||||
for (PlayerToggle toggle : PlayerToggles.get()) {
|
||||
setCommandExecutor(settingCommand, toggle.getName().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
}
|
||||
|
||||
private void setCommandExecutor(@NotNull CommandExecutor executor, String @NotNull ... commands) {
|
||||
for (String commandName : commands) {
|
||||
PluginCommand command = this.getCommand(commandName);
|
||||
if (command != null) {
|
||||
command.setExecutor(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendVersionError(@NotNull Consumer<String> messageMethod) {
|
||||
if (!accessor.isSupported()) {
|
||||
messageMethod.accept("Your server version (" + accessor.getVersion() + ") is not supported.");
|
||||
messageMethod.accept("Please download the correct version of OpenInv here: " + accessor.getReleasesLink());
|
||||
}
|
||||
if (!isSpigot) {
|
||||
messageMethod.accept("OpenInv requires that you use Spigot or a Spigot fork. Per the 1.14 update thread");
|
||||
messageMethod.accept("(https://www.spigotmc.org/threads/369724/ \"A Note on CraftBukkit\"), if you are");
|
||||
messageMethod.accept("encountering an inconsistency with vanilla that prevents you from using Spigot,");
|
||||
messageMethod.accept("that is considered a Spigot bug and should be reported as such.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedVersion() {
|
||||
return this.accessor != null && this.accessor.isSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableSaving() {
|
||||
return config.isSaveDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableOfflineAccess() {
|
||||
return config.isOfflineDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean noArgsOpensSelf() {
|
||||
return config.doesNoArgsOpenSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
return this.accessor.getAnySilentContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAnyContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return PlayerToggles.any().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnyContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
PlayerToggles.any().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSilentContainerStatus(@NotNull final OfflinePlayer offline) {
|
||||
return PlayerToggles.silent().is(offline.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSilentContainerStatus(@NotNull final OfflinePlayer offline, final boolean status) {
|
||||
PlayerToggles.silent().set(offline.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online) {
|
||||
return inventoryManager.getEnderChest(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online) {
|
||||
return inventoryManager.getInventory(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
|
||||
Permissions edit = null;
|
||||
HumanEntity target = inventory.getPlayer();
|
||||
boolean ownContainer = player.equals(target);
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
edit = ownContainer ? Permissions.INVENTORY_EDIT_SELF : Permissions.INVENTORY_EDIT_OTHER;
|
||||
} else if (inventory instanceof ISpecialEnderChest) {
|
||||
edit = ownContainer ? Permissions.ENDERCHEST_EDIT_SELF : Permissions.ENDERCHEST_EDIT_OTHER;
|
||||
}
|
||||
|
||||
boolean viewOnly = edit != null && !edit.hasPermission(player);
|
||||
|
||||
if (ownContainer || viewOnly && config.getAccessEqualMode() != AccessEqualMode.DENY) {
|
||||
return this.accessor.openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
// If the target doesn't have this access level...
|
||||
if (!target.hasPermission(permission)) {
|
||||
// If the viewer does have the access level, all good.
|
||||
if (player.hasPermission(permission)) {
|
||||
break;
|
||||
}
|
||||
// Otherwise check next access level.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the viewer doesn't have an equal access level or equal access is a denial, deny.
|
||||
if (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since this is a tie, setting decides view state.
|
||||
if (config.getAccessEqualMode() == AccessEqualMode.VIEW) {
|
||||
viewOnly = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this.accessor.openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
|
||||
return inventoryManager.getLoadedPlayer(playerUuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
|
||||
return playerLoader.load(offline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable OfflinePlayer matchPlayer(@NotNull String name) {
|
||||
return playerLoader.match(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(@NotNull final OfflinePlayer offline) {
|
||||
inventoryManager.unload(offline.getUniqueId());
|
||||
}
|
||||
@Override
|
||||
public void unload(@NotNull final OfflinePlayer offline) {
|
||||
inventoryManager.unload(offline.getUniqueId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.OpenInv;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.util.InventoryManager;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.PlayerLoader;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ClearInvCommand extends PlayerLookupCommand {
|
||||
|
||||
private final @NotNull InventoryManager manager;
|
||||
|
||||
public ClearInvCommand(
|
||||
@NotNull OpenInv plugin,
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager manager,
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull PlayerLoader playerLoader
|
||||
) {
|
||||
super(plugin, lang, config, playerLoader);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAccessInventory(@NotNull Command command) {
|
||||
return command.getName().equals("clearinv");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable String getTargetIdentifer(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@Nullable String argument,
|
||||
boolean accessInv
|
||||
) {
|
||||
if (!Permissions.CLEAR_OTHER.hasPermission(sender)) {
|
||||
// If the sender does not have permissions to clear others, use self.
|
||||
if (sender instanceof Player player) {
|
||||
return player.getUniqueId().toString();
|
||||
}
|
||||
|
||||
// Console can't target itself. Send error.
|
||||
lang.sendMessage(sender, "messages.error.permissionOpenOther");
|
||||
return null;
|
||||
}
|
||||
|
||||
// If argument is provided, use it.
|
||||
if (argument != null) {
|
||||
return argument;
|
||||
}
|
||||
|
||||
// For players, default to self.
|
||||
if (sender instanceof Player player) {
|
||||
return player.getUniqueId().toString();
|
||||
}
|
||||
|
||||
// For console, argument is required. Send usage.
|
||||
sender.sendMessage(command.getUsage());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable OfflinePlayer getTarget(@NotNull String identifier) {
|
||||
return playerLoader.matchExact(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean deniedCommand(@NotNull CommandSender sender, @NotNull Player onlineTarget, boolean accessInv) {
|
||||
if (onlineTarget.equals(sender)) {
|
||||
return !Permissions.CLEAR_SELF.hasPermission(sender);
|
||||
}
|
||||
return !Permissions.CLEAR_OTHER.hasPermission(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Player onlineTarget,
|
||||
boolean accessInv,
|
||||
@NotNull String @NotNull [] args
|
||||
) {
|
||||
// Create the inventory
|
||||
final ISpecialInventory inv;
|
||||
try {
|
||||
inv = accessInv ? manager.getInventory(onlineTarget) : manager.getEnderChest(onlineTarget);
|
||||
} catch (Exception e) {
|
||||
lang.sendMessage(sender, "messages.error.commandException");
|
||||
plugin.getLogger().log(Level.WARNING, "Unable to create ISpecialInventory", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the inventory
|
||||
inv.getBukkitInventory().clear();
|
||||
manager.save(onlineTarget.getUniqueId());
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
accessInv ? "messages.info.clear.inventory" : "messages.info.clear.enderchest",
|
||||
new Replacement("%target%", onlineTarget.getDisplayName())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!Permissions.CLEAR_OTHER.hasPermission(sender)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return super.onTabComplete(sender, command, label, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,11 +17,11 @@
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import com.lishid.openinv.util.setting.PlayerToggle;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
@@ -36,72 +36,83 @@ import java.util.UUID;
|
||||
|
||||
public class ContainerSettingCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public ContainerSettingCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
public ContainerSettingCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return true;
|
||||
}
|
||||
PlayerToggle toggle = PlayerToggles.get(command.getName());
|
||||
|
||||
PlayerToggle toggle = PlayerToggles.get(command.getName());
|
||||
|
||||
// Shouldn't be possible.
|
||||
if (toggle == null) {
|
||||
JavaPlugin.getProvidingPlugin(getClass()).getLogger().warning("Command /" + command.getName() + " registered with no corresponding toggle!");
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
if (args.length > 0) {
|
||||
args[0] = args[0].toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (args[0].equals("on")) {
|
||||
set(toggle, playerId, true);
|
||||
} else if (args[0].equals("off")) {
|
||||
set(toggle, playerId, false);
|
||||
} else if (!args[0].equals("check")) {
|
||||
// Invalid argument, show usage.
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
set(toggle, playerId, !toggle.is(playerId));
|
||||
}
|
||||
|
||||
String onOff = lang.getLocalizedMessage(player, toggle.is(playerId) ? "messages.info.on" : "messages.info.off");
|
||||
if (onOff == null) {
|
||||
onOff = String.valueOf(toggle.is(playerId));
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.settingState",
|
||||
new Replacement("%setting%", toggle.getName()),
|
||||
new Replacement("%state%", onOff));
|
||||
|
||||
return true;
|
||||
// Shouldn't be possible.
|
||||
if (toggle == null) {
|
||||
JavaPlugin.getProvidingPlugin(getClass()).getLogger().warning("Command /" + command.getName() + " registered with no corresponding toggle!");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void set(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
|
||||
if (toggle.set(uuid, state)) {
|
||||
OpenEvents.notifyPlayerToggle(toggle, uuid, state);
|
||||
}
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
if (args.length > 0) {
|
||||
args[0] = args[0].toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (args[0].equals("on")) {
|
||||
set(toggle, playerId, true);
|
||||
} else if (args[0].equals("off")) {
|
||||
set(toggle, playerId, false);
|
||||
} else if (!args[0].equals("check")) {
|
||||
// Invalid argument, show usage.
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
set(toggle, playerId, !toggle.is(playerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!command.testPermissionSilent(sender) || args.length != 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return TabCompleter.completeString(args[0], new String[] {"check", "on", "off"});
|
||||
String onOff = lang.getLocalizedMessage(player, toggle.is(playerId) ? "messages.info.on" : "messages.info.off");
|
||||
if (onOff == null) {
|
||||
onOff = String.valueOf(toggle.is(playerId));
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.settingState",
|
||||
new Replacement("%setting%", toggle.getName()),
|
||||
new Replacement("%state%", onOff)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void set(@NotNull PlayerToggle toggle, @NotNull UUID uuid, boolean state) {
|
||||
if (toggle.set(uuid, state)) {
|
||||
OpenEvents.notifyPlayerToggle(toggle, uuid, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!command.testPermissionSilent(sender) || args.length != 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return TabCompleter.completeString(args[0], new String[]{"check", "on", "off"});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,232 +18,161 @@ package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.OpenInv;
|
||||
import com.lishid.openinv.internal.ISpecialInventory;
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import com.lishid.openinv.util.InventoryManager;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.PlayerLoader;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import me.nahu.scheduler.wrapper.runnable.WrappedRunnable;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class OpenInvCommand implements TabExecutor {
|
||||
public class OpenInvCommand extends PlayerLookupCommand {
|
||||
|
||||
private final @NotNull OpenInv plugin;
|
||||
private final @NotNull Config config;
|
||||
private final @NotNull InventoryManager manager;
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull PlayerLoader playerLoader;
|
||||
private final HashMap<Player, String> openInvHistory = new HashMap<>();
|
||||
private final HashMap<Player, String> openEnderHistory = new HashMap<>();
|
||||
private final @NotNull InventoryManager manager;
|
||||
private final Map<Player, String> openInvHistory = new WeakHashMap<>();
|
||||
private final Map<Player, String> openEnderHistory = new WeakHashMap<>();
|
||||
|
||||
public OpenInvCommand(
|
||||
@NotNull OpenInv plugin,
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager manager,
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull PlayerLoader playerLoader) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.manager = manager;
|
||||
this.lang = lang;
|
||||
this.playerLoader = playerLoader;
|
||||
public OpenInvCommand(
|
||||
@NotNull OpenInv plugin,
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager manager,
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull PlayerLoader playerLoader
|
||||
) {
|
||||
super(plugin, lang, config, playerLoader);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAccessInventory(@NotNull Command command) {
|
||||
return command.getName().equals("openinv");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable String getTargetIdentifer(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@Nullable String argument,
|
||||
boolean accessInv
|
||||
) {
|
||||
// /openinv help
|
||||
if (accessInv && argument != null && (argument.equalsIgnoreCase("help") || argument.equals("?"))) {
|
||||
this.showHelp(sender);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull final CommandSender sender, @NotNull final Command command, @NotNull final String label, @NotNull final String[] args) {
|
||||
boolean openInv = command.getName().equals("openinv");
|
||||
// Command is player-only.
|
||||
if (!(sender instanceof Player player)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (openInv && args.length > 0 && (args[0].equalsIgnoreCase("help") || args[0].equals("?"))) {
|
||||
this.showHelp(sender);
|
||||
return true;
|
||||
// Use fallthrough for no name provided.
|
||||
if (argument == null) {
|
||||
if (config.doesNoArgsOpenSelf()) {
|
||||
return player.getUniqueId().toString();
|
||||
}
|
||||
return (accessInv ? this.openInvHistory : this.openEnderHistory)
|
||||
.computeIfAbsent(player, localPlayer -> localPlayer.getUniqueId().toString());
|
||||
}
|
||||
|
||||
if (!config.doesNoArgsOpenSelf()) {
|
||||
// History management
|
||||
(accessInv ? this.openInvHistory : this.openEnderHistory).put(player, argument);
|
||||
}
|
||||
|
||||
return argument;
|
||||
}
|
||||
|
||||
private void showHelp(@NotNull CommandSender sender) {
|
||||
// Get registered commands
|
||||
for (String commandName : plugin.getDescription().getCommands().keySet()) {
|
||||
PluginCommand command = plugin.getCommand(commandName);
|
||||
|
||||
// Ensure command is successfully registered and sender can use it
|
||||
if (command == null || !command.testPermissionSilent(sender)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send usage
|
||||
sender.sendMessage(command.getUsage().replace("<command>", commandName));
|
||||
|
||||
List<String> aliases = command.getAliases();
|
||||
if (!aliases.isEmpty()) {
|
||||
// Assemble alias list
|
||||
StringJoiner aliasJoiner = new StringJoiner(", ", " (aliases: ", ")");
|
||||
for (String alias : aliases) {
|
||||
aliasJoiner.add(alias);
|
||||
}
|
||||
|
||||
if (!(sender instanceof Player player)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return true;
|
||||
}
|
||||
// Send all aliases
|
||||
sender.sendMessage(aliasJoiner.toString());
|
||||
}
|
||||
|
||||
String noArgValue;
|
||||
if (config.doesNoArgsOpenSelf()) {
|
||||
noArgValue = player.getUniqueId().toString();
|
||||
} else {
|
||||
// History management
|
||||
noArgValue = (openInv ? this.openInvHistory : this.openEnderHistory).get(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (noArgValue == null || noArgValue.isEmpty()) {
|
||||
noArgValue = player.getUniqueId().toString();
|
||||
(openInv ? this.openInvHistory : this.openEnderHistory).put(player, noArgValue);
|
||||
}
|
||||
}
|
||||
|
||||
final String name;
|
||||
|
||||
if (args.length < 1) {
|
||||
name = noArgValue;
|
||||
} else {
|
||||
name = args[0];
|
||||
}
|
||||
|
||||
new WrappedRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final OfflinePlayer offlinePlayer = playerLoader.match(name);
|
||||
|
||||
if (offlinePlayer == null || (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline())) {
|
||||
lang.sendMessage(player, "messages.error.invalidPlayer");
|
||||
return;
|
||||
}
|
||||
|
||||
new WrappedRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
OpenInvCommand.this.openInventory(player, offlinePlayer, openInv);
|
||||
}
|
||||
}.runTask(OpenInvCommand.this.plugin);
|
||||
|
||||
}
|
||||
}.runTaskAsynchronously(this.plugin);
|
||||
@Override
|
||||
protected @Nullable OfflinePlayer getTarget(@NotNull String identifier) {
|
||||
return playerLoader.match(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean deniedCommand(@NotNull CommandSender sender, @NotNull Player onlineTarget, boolean accessInv) {
|
||||
if (onlineTarget.equals(sender)) {
|
||||
// Permission for opening own inventory.
|
||||
if (!(accessInv ? Permissions.INVENTORY_OPEN_SELF : Permissions.ENDERCHEST_OPEN_SELF).hasPermission(sender)) {
|
||||
lang.sendMessage(sender, "messages.error.permissionOpenSelf");
|
||||
return true;
|
||||
|
||||
}
|
||||
} else {
|
||||
// Permission for opening others' inventories.
|
||||
if (!(accessInv ? Permissions.INVENTORY_OPEN_OTHER : Permissions.ENDERCHEST_OPEN_OTHER).hasPermission(sender)) {
|
||||
lang.sendMessage(sender, "messages.error.permissionOpenOther");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void showHelp(final CommandSender sender) {
|
||||
// Get registered commands
|
||||
for (String commandName : plugin.getDescription().getCommands().keySet()) {
|
||||
PluginCommand command = plugin.getCommand(commandName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure command is successfully registered and sender can use it
|
||||
if (command == null || !command.testPermissionSilent(sender)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send usage
|
||||
sender.sendMessage(command.getUsage().replace("<command>", commandName));
|
||||
|
||||
List<String> aliases = command.getAliases();
|
||||
if (!aliases.isEmpty()) {
|
||||
// Assemble alias list
|
||||
StringJoiner aliasJoiner = new StringJoiner(", ", " (aliases: ", ")");
|
||||
for (String alias : aliases) {
|
||||
aliasJoiner.add(alias);
|
||||
}
|
||||
|
||||
// Send all aliases
|
||||
sender.sendMessage(aliasJoiner.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
protected void handle(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Player target,
|
||||
boolean accessInv,
|
||||
@NotNull String @NotNull [] args
|
||||
) {
|
||||
Player player = (Player) sender;
|
||||
if (!config.doesNoArgsOpenSelf()) {
|
||||
// Record the target
|
||||
(accessInv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
|
||||
}
|
||||
|
||||
private void openInventory(final Player player, final OfflinePlayer target, boolean openinv) {
|
||||
Player onlineTarget;
|
||||
boolean online = target.isOnline();
|
||||
|
||||
if (!online) {
|
||||
if (!config.isOfflineDisabled() && Permissions.ACCESS_OFFLINE.hasPermission(player)) {
|
||||
// Try loading the player's data
|
||||
onlineTarget = playerLoader.load(target);
|
||||
} else {
|
||||
lang.sendMessage(player, "messages.error.permissionPlayerOffline");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Permissions.ACCESS_ONLINE.hasPermission(player)) {
|
||||
onlineTarget = target.getPlayer();
|
||||
} else {
|
||||
lang.sendMessage(player, "messages.error.permissionPlayerOnline");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (onlineTarget == null) {
|
||||
lang.sendMessage(player, "messages.error.invalidPlayer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Permissions checks
|
||||
if (onlineTarget.equals(player)) {
|
||||
// Permission for opening own inventory.
|
||||
if (!(openinv ? Permissions.INVENTORY_OPEN_SELF : Permissions.ENDERCHEST_OPEN_SELF).hasPermission(player)) {
|
||||
lang.sendMessage(player, "messages.error.permissionOpenSelf");
|
||||
return;
|
||||
|
||||
}
|
||||
} else {
|
||||
// Permission for opening others' inventories.
|
||||
if (!(openinv ? Permissions.INVENTORY_OPEN_OTHER : Permissions.ENDERCHEST_OPEN_OTHER).hasPermission(player)) {
|
||||
lang.sendMessage(player, "messages.error.permissionOpenOther");
|
||||
return;
|
||||
}
|
||||
|
||||
// Protected check
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
if (onlineTarget.hasPermission(permission)
|
||||
&& (!player.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY)) {
|
||||
lang.sendMessage(
|
||||
player,
|
||||
"messages.error.permissionExempt",
|
||||
new Replacement("%target%", onlineTarget.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Crossworld check
|
||||
if (!Permissions.ACCESS_CROSSWORLD.hasPermission(player)
|
||||
&& !onlineTarget.getWorld().equals(player.getWorld())) {
|
||||
lang.sendMessage(
|
||||
player,
|
||||
"messages.error.permissionCrossWorld",
|
||||
new Replacement("%target%", onlineTarget.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.doesNoArgsOpenSelf()) {
|
||||
// Record the target
|
||||
(openinv ? this.openInvHistory : this.openEnderHistory).put(player, target.getUniqueId().toString());
|
||||
}
|
||||
|
||||
// Create the inventory
|
||||
final ISpecialInventory inv;
|
||||
try {
|
||||
inv = openinv ? manager.getInventory(onlineTarget) : manager.getEnderChest(onlineTarget);
|
||||
} catch (Exception e) {
|
||||
lang.sendMessage(player, "messages.error.commandException");
|
||||
plugin.getLogger().log(Level.WARNING, "Unable to create ISpecialInventory", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the inventory
|
||||
plugin.openInventory(player, inv);
|
||||
// Create the inventory
|
||||
final ISpecialInventory inv;
|
||||
try {
|
||||
inv = accessInv ? manager.getInventory(target) : manager.getEnderChest(target);
|
||||
} catch (Exception e) {
|
||||
lang.sendMessage(player, "messages.error.commandException");
|
||||
plugin.getLogger().log(Level.WARNING, "Unable to create ISpecialInventory", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!command.testPermissionSilent(sender) || args.length != 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return TabCompleter.completeOnlinePlayer(sender, args[0]);
|
||||
}
|
||||
// Open the inventory
|
||||
plugin.openInventory(player, inv);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package com.lishid.openinv.command;
|
||||
|
||||
import com.lishid.openinv.OpenInv;
|
||||
import com.lishid.openinv.util.AccessEqualMode;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.PlayerLoader;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.config.Config;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import me.nahu.scheduler.wrapper.runnable.WrappedRunnable;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A command abstraction for performing actions after looking up and loading a player.
|
||||
*/
|
||||
public abstract class PlayerLookupCommand implements TabExecutor {
|
||||
|
||||
protected final @NotNull OpenInv plugin;
|
||||
protected final @NotNull LanguageManager lang;
|
||||
protected final @NotNull Config config;
|
||||
protected final @NotNull PlayerLoader playerLoader;
|
||||
|
||||
public PlayerLookupCommand(
|
||||
@NotNull OpenInv plugin,
|
||||
@NotNull LanguageManager lang,
|
||||
@NotNull Config config,
|
||||
@NotNull PlayerLoader playerLoader
|
||||
) {
|
||||
this.plugin = plugin;
|
||||
this.lang = lang;
|
||||
this.config = config;
|
||||
this.playerLoader = playerLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String @NotNull [] args
|
||||
) {
|
||||
|
||||
// Inventory or ender chest?
|
||||
boolean accessInv = isAccessInventory(command);
|
||||
|
||||
// Get target identifier from parameters.
|
||||
String targetId = getTargetIdentifer(sender, command, args.length > 0 ? args[0] : null, accessInv);
|
||||
if (targetId == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
new WrappedRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Get target from identifier.
|
||||
final OfflinePlayer target = getTarget(targetId);
|
||||
|
||||
if (target == null || (!target.hasPlayedBefore() && !target.isOnline())) {
|
||||
lang.sendMessage(sender, "messages.error.invalidPlayer");
|
||||
return;
|
||||
}
|
||||
|
||||
new WrappedRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Ensure sender still exists.
|
||||
if ((sender instanceof Player player) && !player.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform access checks and load target if necessary.
|
||||
Player onlineTarget = access(sender, target, accessInv);
|
||||
|
||||
if (onlineTarget != null) {
|
||||
handle(sender, onlineTarget, accessInv, args);
|
||||
}
|
||||
}
|
||||
}.runTask(PlayerLookupCommand.this.plugin);
|
||||
|
||||
}
|
||||
}.runTaskAsynchronously(this.plugin);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a player inventory or ender chest is accessed by the {@link Command} executed.
|
||||
*
|
||||
* @param command the {@code Command} being executed
|
||||
* @return {@code true} if the command is for inventories, {@code false} for ender chests
|
||||
*/
|
||||
protected abstract boolean isAccessInventory(@NotNull Command command);
|
||||
|
||||
/**
|
||||
* Determine the target identifier from the first command argument.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @param sender the sender of the command
|
||||
* @param argument the argument, or {@code null} if none provided
|
||||
* @param accessInv {@code true} if an inventory is being accessed, {@code false} for ender chest
|
||||
* @return an updated target identifier or {@code null} if no target is available
|
||||
*/
|
||||
protected abstract @Nullable String getTargetIdentifer(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@Nullable String argument,
|
||||
boolean accessInv
|
||||
);
|
||||
|
||||
/**
|
||||
* Get an {@link OfflinePlayer} by identifier.
|
||||
*
|
||||
* @param identifier the identifier
|
||||
* @return the corresponding player or {@code null} if no match was found
|
||||
*/
|
||||
protected abstract @Nullable OfflinePlayer getTarget(@NotNull String identifier);
|
||||
|
||||
/**
|
||||
* Attempt to access the target as an online player. Performs feedback in the event of denial.
|
||||
*
|
||||
* @param sender the {@link CommandSender} attempting access
|
||||
* @param target the {@link OfflinePlayer} being targeted by the command
|
||||
* @param invPerms {@code true} to use inventory permissions, {@code false} for ender chest
|
||||
* @return the {@link Player} loaded or {@code null} if target is not accessible
|
||||
*/
|
||||
protected @Nullable Player access(@NotNull CommandSender sender, @NotNull OfflinePlayer target, boolean invPerms) {
|
||||
// Attempt to load online player dependent on permissions and configuration.
|
||||
Player onlineTarget = accessAsPlayer(sender, target);
|
||||
|
||||
if (onlineTarget == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Permissions checks.
|
||||
if (deniedCommand(sender, onlineTarget, invPerms) || deniedAccess(sender, onlineTarget)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return onlineTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for accessing target as an online {@link Player}. Performs checks
|
||||
* and feedback for configuration and online/offline permissions.
|
||||
*
|
||||
* @param sender the {@link CommandSender} attempting access
|
||||
* @param target the {@link OfflinePlayer} being targeted by the command
|
||||
* @return the {@link Player} loaded or {@code null} if target is not accessible
|
||||
*/
|
||||
protected @Nullable Player accessAsPlayer(@NotNull CommandSender sender, @NotNull OfflinePlayer target) {
|
||||
Player onlineTarget;
|
||||
|
||||
if (!target.isOnline()) {
|
||||
if (!config.isOfflineDisabled() && Permissions.ACCESS_OFFLINE.hasPermission(sender)) {
|
||||
// Try loading the player's data.
|
||||
onlineTarget = playerLoader.load(target);
|
||||
} else {
|
||||
lang.sendMessage(sender, "messages.error.permissionPlayerOffline");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (Permissions.ACCESS_ONLINE.hasPermission(sender)) {
|
||||
onlineTarget = target.getPlayer();
|
||||
} else {
|
||||
lang.sendMessage(sender, "messages.error.permissionPlayerOnline");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (onlineTarget == null) {
|
||||
lang.sendMessage(sender, "messages.error.invalidPlayer");
|
||||
return null;
|
||||
}
|
||||
|
||||
return onlineTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a lack of permissions related to the specific command being executed for the sender.
|
||||
* For example, {@link Permissions#INVENTORY_OPEN_OTHER} might be required if the target and sender differ.
|
||||
*
|
||||
* @param sender the {@link CommandSender} attempting access
|
||||
* @param onlineTarget the {@link Player} being targeted by the command
|
||||
* @param accessInv {@code true} to use inventory permissions, {@code false} for ender chest
|
||||
* @return {@code true} if the sender does not have the correct execution-specific permission
|
||||
*/
|
||||
protected abstract boolean deniedCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Player onlineTarget,
|
||||
boolean accessInv
|
||||
);
|
||||
|
||||
/**
|
||||
* Check for a lack of generalized permissions for accessing the target.
|
||||
* By default, this is access levels and cross-world restrictions.
|
||||
*
|
||||
* @param sender the {@link CommandSender} attempting access
|
||||
* @param onlineTarget the {@link Player} being targeted by the command
|
||||
* @return {@code true} if the sender does not have access to the target
|
||||
*/
|
||||
protected boolean deniedAccess(@NotNull CommandSender sender, @NotNull Player onlineTarget) {
|
||||
if (sender.equals(onlineTarget)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Protected check
|
||||
for (int level = 4; level > 0; --level) {
|
||||
String permission = "openinv.access.level." + level;
|
||||
if (onlineTarget.hasPermission(permission)
|
||||
&& (!sender.hasPermission(permission) || config.getAccessEqualMode() == AccessEqualMode.DENY)) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.permissionExempt",
|
||||
new Replacement("%target%", onlineTarget.getDisplayName())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Crossworld check
|
||||
if (sender instanceof Player player
|
||||
&& !Permissions.ACCESS_CROSSWORLD.hasPermission(sender)
|
||||
&& !onlineTarget.getWorld().equals(player.getWorld())) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.permissionCrossWorld",
|
||||
new Replacement("%target%", onlineTarget.getDisplayName())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform main command functionality.
|
||||
*
|
||||
* @param sender the {@link CommandSender} executing the command
|
||||
* @param target the {@link Player} being targeted
|
||||
* @param accessInv {@code true} if an inventory is being accessed, {@code false} for ender chest
|
||||
* @param args the original command arguments
|
||||
*/
|
||||
protected abstract void handle(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Player target,
|
||||
boolean accessInv,
|
||||
@NotNull String @NotNull [] args
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!command.testPermissionSilent(sender) || args.length != 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return TabCompleter.completeOnlinePlayer(sender, args[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.lishid.openinv.command;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import com.lishid.openinv.util.SearchHelper;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
@@ -40,106 +41,119 @@ import java.util.Locale;
|
||||
*/
|
||||
public class SearchContainerCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull Plugin plugin;
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull Plugin plugin;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public SearchContainerCommand(@NotNull Plugin plugin, @NotNull LanguageManager lang) {
|
||||
this.plugin = plugin;
|
||||
this.lang = lang;
|
||||
public SearchContainerCommand(@NotNull Plugin plugin, @NotNull LanguageManager lang) {
|
||||
this.plugin = plugin;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!(sender instanceof Player senderPlayer)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!(sender instanceof Player senderPlayer)) {
|
||||
lang.sendMessage(sender, "messages.error.consoleUnsupported");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
// Must supply material
|
||||
return false;
|
||||
}
|
||||
|
||||
Material material = Material.matchMaterial(args[0]);
|
||||
|
||||
if (material == null) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.invalidMaterial",
|
||||
new Replacement("%target%", args[0]));
|
||||
return false;
|
||||
}
|
||||
|
||||
int radius = 5;
|
||||
|
||||
if (args.length > 1) {
|
||||
try {
|
||||
radius = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
// Invalid radius supplied
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp radius.
|
||||
int configMax = plugin.getConfig().getInt("settings.command.searchcontainer.max-radius", 10);
|
||||
radius = Math.max(0, Math.min(radius, configMax));
|
||||
|
||||
World world = senderPlayer.getWorld();
|
||||
Chunk centerChunk = senderPlayer.getLocation().getChunk();
|
||||
StringBuilder locations = new StringBuilder();
|
||||
|
||||
for (int dX = -radius; dX <= radius; ++dX) {
|
||||
for (int dZ = -radius; dZ <= radius; ++dZ) {
|
||||
if (!world.loadChunk(centerChunk.getX() + dX, centerChunk.getZ() + dZ, false)) {
|
||||
continue;
|
||||
}
|
||||
Chunk chunk = world.getChunkAt(centerChunk.getX() + dX, centerChunk.getZ() + dZ);
|
||||
for (BlockState tileEntity : chunk.getTileEntities()) {
|
||||
if (!(tileEntity instanceof InventoryHolder holder)) {
|
||||
continue;
|
||||
}
|
||||
if (!holder.getInventory().contains(material)) {
|
||||
continue;
|
||||
}
|
||||
locations.append(holder.getInventory().getType().name().toLowerCase(Locale.ENGLISH)).append(" (")
|
||||
.append(tileEntity.getX()).append(',').append(tileEntity.getY()).append(',')
|
||||
.append(tileEntity.getZ()).append("), ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matches found, delete trailing comma and space
|
||||
if (!locations.isEmpty()) {
|
||||
locations.delete(locations.length() - 2, locations.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.container.noMatches",
|
||||
new Replacement("%target%", material.name()));
|
||||
return true;
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.container.matches",
|
||||
new Replacement("%target%", material.name()),
|
||||
new Replacement("%detail%", locations.toString()));
|
||||
return true;
|
||||
if (args.length < 1) {
|
||||
// Must supply material
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Material material = Material.matchMaterial(args[0]);
|
||||
|
||||
String argument = args[args.length - 1];
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeEnum(argument, Material.class);
|
||||
} else {
|
||||
return TabCompleter.completeInteger(argument);
|
||||
}
|
||||
if (material == null) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.invalidMaterial",
|
||||
new Replacement("%target%", args[0])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
int radius = 5;
|
||||
|
||||
if (args.length > 1) {
|
||||
try {
|
||||
radius = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
// Invalid radius supplied
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp radius.
|
||||
int configMax = plugin.getConfig().getInt("settings.command.searchcontainer.max-radius", 10);
|
||||
radius = Math.max(0, Math.min(radius, configMax));
|
||||
|
||||
World world = senderPlayer.getWorld();
|
||||
Chunk centerChunk = senderPlayer.getLocation().getChunk();
|
||||
StringBuilder locations = new StringBuilder();
|
||||
|
||||
for (int dX = -radius; dX <= radius; ++dX) {
|
||||
for (int dZ = -radius; dZ <= radius; ++dZ) {
|
||||
if (!world.loadChunk(centerChunk.getX() + dX, centerChunk.getZ() + dZ, false)) {
|
||||
continue;
|
||||
}
|
||||
Chunk chunk = world.getChunkAt(centerChunk.getX() + dX, centerChunk.getZ() + dZ);
|
||||
for (BlockState tileEntity : chunk.getTileEntities()) {
|
||||
if (!(tileEntity instanceof InventoryHolder holder)) {
|
||||
continue;
|
||||
}
|
||||
if (!SearchHelper.findMatch(holder.getInventory(), itemStack -> itemStack.getType() == material)) {
|
||||
continue;
|
||||
}
|
||||
locations.append(holder.getInventory().getType().name().toLowerCase(Locale.ENGLISH)).append(" (")
|
||||
.append(tileEntity.getX()).append(',').append(tileEntity.getY()).append(',')
|
||||
.append(tileEntity.getZ()).append("), ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matches found, delete trailing comma and space
|
||||
if (!locations.isEmpty()) {
|
||||
locations.delete(locations.length() - 2, locations.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.container.noMatches",
|
||||
new Replacement("%target%", material.name())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.container.matches",
|
||||
new Replacement("%target%", material.name()),
|
||||
new Replacement("%detail%", locations.toString())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
String[] args
|
||||
) {
|
||||
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String argument = args[args.length - 1];
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeEnum(argument, Material.class);
|
||||
} else {
|
||||
return TabCompleter.completeInteger(argument);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,8 @@ package com.lishid.openinv.command;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import com.lishid.openinv.util.SearchHelper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.command.Command;
|
||||
@@ -29,7 +29,6 @@ import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -46,127 +45,142 @@ import java.util.Locale;
|
||||
*/
|
||||
public class SearchEnchantCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public SearchEnchantCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
public SearchEnchantCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (args.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
Enchantment enchant = null;
|
||||
int level = 0;
|
||||
|
||||
for (String argument : args) {
|
||||
try {
|
||||
level = Integer.parseInt(argument);
|
||||
continue;
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Not a level being specified.
|
||||
}
|
||||
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
NamespacedKey key = NamespacedKey.fromString(argument);
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Enchantment localEnchant = Registry.ENCHANTMENT.get(key);
|
||||
if (localEnchant != null) {
|
||||
enchant = localEnchant;
|
||||
}
|
||||
}
|
||||
|
||||
// Arguments not set correctly
|
||||
if (level == 0 && enchant == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder players = new StringBuilder();
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
boolean flagInventory = containsEnchantment(player.getInventory(), enchant, level);
|
||||
boolean flagEnder = containsEnchantment(player.getEnderChest(), enchant, level);
|
||||
|
||||
// No matches, continue
|
||||
if (!flagInventory && !flagEnder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Matches, append details
|
||||
players.append(player.getName()).append(" (");
|
||||
if (flagInventory) {
|
||||
players.append("inv");
|
||||
}
|
||||
if (flagEnder) {
|
||||
if (flagInventory) {
|
||||
players.append(',');
|
||||
}
|
||||
players.append("ender");
|
||||
}
|
||||
players.append("), ");
|
||||
}
|
||||
|
||||
if (!players.isEmpty()) {
|
||||
// Matches found, delete trailing comma and space
|
||||
players.delete(players.length() - 2, players.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.noMatches",
|
||||
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.matches",
|
||||
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level),
|
||||
new Replacement("%detail%", players.toString())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsEnchantment(Inventory inventory, @Nullable Enchantment enchant, int minLevel) {
|
||||
return SearchHelper.findMatch(
|
||||
inventory,
|
||||
itemStack -> {
|
||||
// Ensure meta is available and has enchantments.
|
||||
if (!itemStack.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Enchantment enchant = null;
|
||||
int level = 0;
|
||||
|
||||
for (String argument : args) {
|
||||
try {
|
||||
level = Integer.parseInt(argument);
|
||||
continue;
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Not a level being specified.
|
||||
}
|
||||
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
NamespacedKey key = NamespacedKey.fromString(argument);
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Enchantment localEnchant = Registry.ENCHANTMENT.get(key);
|
||||
if (localEnchant != null) {
|
||||
enchant = localEnchant;
|
||||
}
|
||||
}
|
||||
|
||||
// Arguments not set correctly
|
||||
if (level == 0 && enchant == null) {
|
||||
}
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
if (meta == null || !meta.hasEnchants()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder players = new StringBuilder();
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
boolean flagInventory = containsEnchantment(player.getInventory(), enchant, level);
|
||||
boolean flagEnder = containsEnchantment(player.getEnderChest(), enchant, level);
|
||||
// If enchantment is provided, use it.
|
||||
if (enchant != null) {
|
||||
return meta.getEnchantLevel(enchant) >= minLevel;
|
||||
}
|
||||
|
||||
// No matches, continue
|
||||
if (!flagInventory && !flagEnder) {
|
||||
continue;
|
||||
// Otherwise, check all enchantment levels.
|
||||
for (int enchLevel : meta.getEnchants().values()) {
|
||||
if (enchLevel >= minLevel) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Matches, append details
|
||||
players.append(player.getName()).append(" (");
|
||||
if (flagInventory) {
|
||||
players.append("inv");
|
||||
}
|
||||
if (flagEnder) {
|
||||
if (flagInventory) {
|
||||
players.append(',');
|
||||
}
|
||||
players.append("ender");
|
||||
}
|
||||
players.append("), ");
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!players.isEmpty()) {
|
||||
// Matches found, delete trailing comma and space
|
||||
players.delete(players.length() - 2, players.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.noMatches",
|
||||
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level));
|
||||
return true;
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.matches",
|
||||
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level),
|
||||
new Replacement("%detail%", players.toString()));
|
||||
return true;
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (!command.testPermissionSilent(sender) || args.length < 1 || args.length > 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean containsEnchantment(Inventory inventory, @Nullable Enchantment enchant, int minLevel) {
|
||||
for (ItemStack item : inventory.getContents()) {
|
||||
if (item == null || item.getType() == Material.AIR) {
|
||||
continue;
|
||||
}
|
||||
if (enchant != null) {
|
||||
if (item.containsEnchantment(enchant) && item.getEnchantmentLevel(enchant) >= minLevel) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!item.hasItemMeta()) {
|
||||
continue;
|
||||
}
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null || !meta.hasEnchants()) {
|
||||
continue;
|
||||
}
|
||||
for (int enchLevel : meta.getEnchants().values()) {
|
||||
if (enchLevel >= minLevel) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (!command.testPermissionSilent(sender) || args.length < 1 || args.length > 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeObject(args[0], enchantment -> enchantment.getKey().toString(), Registry.ENCHANTMENT.stream().toArray(Enchantment[]::new));
|
||||
} else {
|
||||
return TabCompleter.completeInteger(args[1]);
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeObject(args[0], enchantment -> enchantment.getKey().toString(), Registry.ENCHANTMENT.stream().toArray(Enchantment[]::new));
|
||||
} else {
|
||||
return TabCompleter.completeInteger(args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.lishid.openinv.command;
|
||||
import com.lishid.openinv.util.TabCompleter;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.lang.Replacement;
|
||||
import com.lishid.openinv.util.SearchHelper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
@@ -26,98 +27,117 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SearchInvCommand implements TabExecutor {
|
||||
|
||||
private final @NotNull LanguageManager lang;
|
||||
private final @NotNull LanguageManager lang;
|
||||
|
||||
public SearchInvCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
public SearchInvCommand(@NotNull LanguageManager lang) {
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull Command command,
|
||||
@NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
|
||||
Material material = null;
|
||||
|
||||
if (args.length >= 1) {
|
||||
material = Material.matchMaterial(args[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (material == null) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.invalidMaterial",
|
||||
new Replacement("%target%", args.length > 0 ? args[0] : "null")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Material material = null;
|
||||
|
||||
if (args.length >= 1) {
|
||||
material = Material.matchMaterial(args[0]);
|
||||
}
|
||||
|
||||
if (material == null) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.invalidMaterial",
|
||||
new Replacement("%target%", args.length > 0 ? args[0] : "null"));
|
||||
return false;
|
||||
}
|
||||
|
||||
int count = 1;
|
||||
|
||||
if (args.length >= 2) {
|
||||
try {
|
||||
count = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException ex) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.error.invalidNumber",
|
||||
new Replacement("%target%", args[1]));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder players = new StringBuilder();
|
||||
boolean searchInv = command.getName().equals("searchinv");
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
Inventory inventory = searchInv ? player.getInventory() : player.getEnderChest();
|
||||
int total = 0;
|
||||
for (ItemStack itemStack : inventory.getContents()) {
|
||||
if (itemStack != null && itemStack.getType() == material) {
|
||||
total += itemStack.getAmount();
|
||||
if (total >= count) {
|
||||
players.append(player.getName()).append(", ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matches found, delete trailing comma and space
|
||||
if (!players.isEmpty()) {
|
||||
players.delete(players.length() - 2, players.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.noMatches",
|
||||
new Replacement("%target%", material.name()));
|
||||
return true;
|
||||
}
|
||||
int count = 1;
|
||||
|
||||
if (args.length >= 2) {
|
||||
try {
|
||||
count = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException ex) {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.matches",
|
||||
new Replacement("%target%", material.name()),
|
||||
new Replacement("%detail%", players.toString()));
|
||||
return true;
|
||||
sender,
|
||||
"messages.error.invalidNumber",
|
||||
new Replacement("%target%", args[1])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String argument = args[args.length - 1];
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeEnum(argument, Material.class);
|
||||
} else {
|
||||
return TabCompleter.completeInteger(argument);
|
||||
}
|
||||
StringBuilder players = new StringBuilder();
|
||||
boolean searchInv = command.getName().equals("searchinv");
|
||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||
Inventory inventory = searchInv ? player.getInventory() : player.getEnderChest();
|
||||
if (findMatch(inventory, material, count)) {
|
||||
players.append(player.getName()).append(", ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Matches found, delete trailing comma and space
|
||||
if (!players.isEmpty()) {
|
||||
players.delete(players.length() - 2, players.length());
|
||||
} else {
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.noMatches",
|
||||
new Replacement("%target%", material.name())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
lang.sendMessage(
|
||||
sender,
|
||||
"messages.info.player.matches",
|
||||
new Replacement("%target%", material.name()),
|
||||
new Replacement("%detail%", players.toString())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean findMatch(@NotNull Inventory inventory, @NotNull Material material, int count) {
|
||||
AtomicInteger total = new AtomicInteger();
|
||||
return SearchHelper.findMatch(
|
||||
inventory,
|
||||
itemStack -> {
|
||||
if (itemStack.getType() == material && itemStack.getAmount() > 0) {
|
||||
return total.addAndGet(itemStack.getAmount()) >= count;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
@NotNull String[] args
|
||||
) {
|
||||
if (args.length < 1 || args.length > 2 || !command.testPermissionSilent(sender)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String argument = args[args.length - 1];
|
||||
if (args.length == 1) {
|
||||
return TabCompleter.completeEnum(argument, Material.class);
|
||||
} else {
|
||||
return TabCompleter.completeInteger(argument);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,8 +20,8 @@ import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.internal.ViewOnly;
|
||||
import com.lishid.openinv.util.InternalAccessor;
|
||||
import com.lishid.openinv.util.Permissions;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import com.lishid.openinv.util.lang.LanguageManager;
|
||||
import com.lishid.openinv.util.setting.PlayerToggles;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -35,236 +35,259 @@ import java.util.logging.Logger;
|
||||
|
||||
public class InternalAccessor {
|
||||
|
||||
private static final boolean PAPER;
|
||||
private static final boolean PAPER;
|
||||
|
||||
static {
|
||||
boolean paper = false;
|
||||
try {
|
||||
Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||
paper = true;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Expect remapped server.
|
||||
}
|
||||
PAPER = paper;
|
||||
static {
|
||||
boolean paper = false;
|
||||
try {
|
||||
Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||
paper = true;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Expect remapped server.
|
||||
}
|
||||
PAPER = paper;
|
||||
}
|
||||
|
||||
private @Nullable Accessor internal;
|
||||
|
||||
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
try {
|
||||
internal = getAccessor(logger, lang);
|
||||
|
||||
if (internal != null) {
|
||||
InventoryAccess.setProvider(internal::get);
|
||||
}
|
||||
} catch (NoClassDefFoundError | Exception e) {
|
||||
internal = null;
|
||||
InventoryAccess.setProvider(null);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Accessor getAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
Version maxSupported = Version.of(1, 21, 8);
|
||||
Version minSupported = Version.of(1, 21, 1);
|
||||
|
||||
// Ensure version is in supported range.
|
||||
if (BukkitVersions.MINECRAFT.greaterThan(maxSupported) || BukkitVersions.MINECRAFT.lessThan(minSupported)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Accessor internal;
|
||||
|
||||
public InternalAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
try {
|
||||
internal = getAccessor(logger, lang);
|
||||
|
||||
if (internal != null) {
|
||||
InventoryAccess.setProvider(internal::get);
|
||||
}
|
||||
} catch (NoClassDefFoundError | Exception e) {
|
||||
internal = null;
|
||||
InventoryAccess.setProvider(null);
|
||||
}
|
||||
// Load Spigot accessor.
|
||||
if (!PAPER) {
|
||||
if (BukkitVersions.MINECRAFT.equals(maxSupported) || BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 7))) {
|
||||
// Current Spigot, remapped internals are available.
|
||||
return new com.lishid.openinv.internal.reobf.InternalAccessor(logger, lang);
|
||||
} else {
|
||||
// Older Spigot; unsupported.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Accessor getAccessor(@NotNull Logger logger, @NotNull LanguageManager lang) {
|
||||
Version maxSupported = Version.of(1, 21, 5);
|
||||
Version minSupported = Version.of(1, 21, 1);
|
||||
|
||||
// Ensure version is in supported range.
|
||||
if (BukkitVersions.MINECRAFT.greaterThan(maxSupported) || BukkitVersions.MINECRAFT.lessThan(minSupported)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load Spigot accessor.
|
||||
if (!PAPER) {
|
||||
if (BukkitVersions.MINECRAFT.equals(maxSupported)) {
|
||||
// Current Spigot, remapped internals are available.
|
||||
return new com.lishid.openinv.internal.reobf.InternalAccessor(logger, lang);
|
||||
} else {
|
||||
// Older Spigot; unsupported.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Paper or a Paper fork, can use Mojang-mapped internals.
|
||||
if (BukkitVersions.MINECRAFT.equals(maxSupported)) { // 1.21.5
|
||||
return new com.lishid.openinv.internal.common.InternalAccessor(logger, lang);
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 4))) { // 1.21.4
|
||||
return new com.lishid.openinv.internal.paper1_21_4.InternalAccessor(logger, lang);
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21, 2))) {
|
||||
// 1.21.1-1.21.2 placeholder format
|
||||
return new com.lishid.openinv.internal.paper1_21_1.InternalAccessor(logger, lang);
|
||||
}
|
||||
|
||||
// 1.21.2, 1.21.3
|
||||
return new com.lishid.openinv.internal.paper1_21_3.InternalAccessor(logger, lang);
|
||||
// Paper or a Paper fork, can use Mojang-mapped internals.
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(maxSupported)
|
||||
&& BukkitVersions.MINECRAFT.greaterThanOrEqual(Version.of(1, 21, 6))) { // 1.21.6, 1.21.7, 1.21.8
|
||||
return new com.lishid.openinv.internal.common.InternalAccessor(logger, lang);
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 5))) { // 1.21.5
|
||||
return new com.lishid.openinv.internal.paper1_21_5.InternalAccessor(logger, lang);
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 21, 4))) { // 1.21.4
|
||||
return new com.lishid.openinv.internal.paper1_21_4.InternalAccessor(logger, lang);
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21, 2))) {
|
||||
// 1.21.1-1.21.2 placeholder format
|
||||
return new com.lishid.openinv.internal.paper1_21_1.InternalAccessor(logger, lang);
|
||||
}
|
||||
|
||||
public String getReleasesLink() {
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 4, 4))) { // Good luck.
|
||||
return "https://dev.bukkit.org/projects/openinv/files?&sort=datecreated";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 8, 8))) { // 1.8.8
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 13))) { // 1.4.4+ had versioned packages.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0 (OpenInv-legacy)";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 13))) { // 1.13
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 14))) { // 1.13.1, 1.13.2
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.7";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14))) { // 1.14 to 1.14.1 had no revision bump.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14, 1))) { // 1.14.1 to 1.14.2 had no revision bump.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 15))) { // 1.14.2
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 15, 1))) { // 1.15, 1.15.1
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 16))) { // 1.15.2
|
||||
return "https://github.com/Jikoo/OpenInv/commit/502f661be39ee85d300851dd571f3da226f12345 (never released)";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 1))) { // 1.16, 1.16.1
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.4";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 3))) { // 1.16.2, 1.16.3
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 17))) { // 1.16.4, 1.16.5
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 18, 1))) { // 1.17, 1.18, 1.18.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 19))) { // 1.18.2
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19))) { // 1.19
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19, 1))) { // 1.19.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.2";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 19, 3))) { // 1.19.2, 1.19.3
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 20))) { // 1.19.4
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 1))) { // 1.20, 1.20.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 3))) { // 1.20.2, 1.20.3
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 5))) { // 1.20.5
|
||||
return "Unsupported; upgrade to 1.20.6: https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
|
||||
}
|
||||
if (PAPER && BukkitVersions.MINECRAFT.greaterThanOrEqual(Version.of(1, 21, 1))) { // Paper 1.21.1-1.21.3
|
||||
return "https://github.com/Jikoo/OpenInv/releases";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21))) { // 1.20.4, 1.20.6, 1.21
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 2))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 3))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.6";
|
||||
}
|
||||
return "https://github.com/Jikoo/OpenInv/releases";
|
||||
// 1.21.2, 1.21.3
|
||||
return new com.lishid.openinv.internal.paper1_21_3.InternalAccessor(logger, lang);
|
||||
}
|
||||
|
||||
public String getReleasesLink() {
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 4, 4))) { // Good luck.
|
||||
return "https://dev.bukkit.org/projects/openinv/files?&sort=datecreated";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 8, 8))) { // 1.8.8
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 13))) { // 1.4.4+ had versioned packages.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0 (OpenInv-legacy)";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 13))) { // 1.13
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 14))) { // 1.13.1, 1.13.2
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.7";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14))) { // 1.14 to 1.14.1 had no revision bump.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 14, 1))) { // 1.14.1 to 1.14.2 had no revision bump.
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.0.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 15))) { // 1.14.2
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 15, 1))) { // 1.15, 1.15.1
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 16))) { // 1.15.2
|
||||
return "https://github.com/Jikoo/OpenInv/commit/502f661be39ee85d300851dd571f3da226f12345 (never released)";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 1))) { // 1.16, 1.16.1
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.4";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 16, 3))) { // 1.16.2, 1.16.3
|
||||
return "https://github.com/lishid/OpenInv/releases/tag/4.1.5";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 17))) { // 1.16.4, 1.16.5
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.8";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 18, 1))) { // 1.17, 1.18, 1.18.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 19))) { // 1.18.2
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19))) { // 1.19
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 19, 1))) { // 1.19.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.2.2";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 19, 3))) { // 1.19.2, 1.19.3
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 20))) { // 1.19.4
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 1))) { // 1.20, 1.20.1
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.1";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 20, 3))) { // 1.20.2, 1.20.3
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/4.4.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.equals(Version.of(1, 20, 5))) { // 1.20.5
|
||||
return "Unsupported; upgrade to 1.20.6: https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21))) { // 1.20.4, 1.20.6, 1.21
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.2";
|
||||
}
|
||||
if (!PAPER) {
|
||||
return getSpigotReleaseLink();
|
||||
}
|
||||
// Paper 1.21.1-1.21.7
|
||||
return "https://github.com/Jikoo/OpenInv/releases";
|
||||
}
|
||||
|
||||
private String getSpigotReleaseLink() {
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 2))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.3";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 3))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.6";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 4))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.9";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 5))) {
|
||||
return "https://github.com/Jikoo/OpenInv/releases/tag/5.1.11";
|
||||
}
|
||||
if (BukkitVersions.MINECRAFT.lessThanOrEqual(Version.of(1, 21, 6))) {
|
||||
return "Unsupported; upgrade to 1.21.7: https://github.com/Jikoo/OpenInv/releases";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload internal features.
|
||||
*/
|
||||
public void reload(ConfigurationSection config) {
|
||||
if (internal != null) {
|
||||
internal.reload(config);
|
||||
}
|
||||
}
|
||||
return "https://github.com/Jikoo/OpenInv/releases";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server implementation version.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
public @NotNull String getVersion() {
|
||||
return BukkitVersions.MINECRAFT.toString();
|
||||
/**
|
||||
* Reload internal features.
|
||||
*/
|
||||
public void reload(ConfigurationSection config) {
|
||||
if (internal != null) {
|
||||
internal.reload(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server implementation is supported.
|
||||
*
|
||||
* @return true if initialized for a supported server version
|
||||
*/
|
||||
public boolean isSupported() {
|
||||
return internal != null;
|
||||
}
|
||||
/**
|
||||
* Gets the server implementation version.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
public @NotNull String getVersion() {
|
||||
return BukkitVersions.MINECRAFT.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the IAnySilentContainer implementation for the current server version.
|
||||
*
|
||||
* @return the IAnySilentContainer
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getAnySilentContainer();
|
||||
}
|
||||
/**
|
||||
* Checks if the server implementation is supported.
|
||||
*
|
||||
* @return true if initialized for a supported server version
|
||||
*/
|
||||
public boolean isSupported() {
|
||||
return internal != null;
|
||||
}
|
||||
|
||||
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory, boolean viewOnly) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager().openInventory(player, inventory, viewOnly);
|
||||
/**
|
||||
* Get the instance of the IAnySilentContainer implementation for the current server version.
|
||||
*
|
||||
* @return the IAnySilentContainer
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
public @NotNull IAnySilentContainer getAnySilentContainer() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getAnySilentContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the IPlayerDataManager implementation for the current server version.
|
||||
*
|
||||
* @return the IPlayerDataManager
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
@NotNull PlayerManager getPlayerDataManager() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager();
|
||||
public @Nullable InventoryView openInventory(
|
||||
@NotNull Player player,
|
||||
@NotNull ISpecialInventory inventory,
|
||||
boolean viewOnly
|
||||
) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager().openInventory(player, inventory, viewOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the ISpecialEnderChest implementation for the given Player.
|
||||
*
|
||||
* @param player the Player
|
||||
* @return the ISpecialEnderChest created
|
||||
*/
|
||||
ISpecialEnderChest createEnderChest(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.createEnderChest(player);
|
||||
/**
|
||||
* Get the instance of the IPlayerDataManager implementation for the current server version.
|
||||
*
|
||||
* @return the IPlayerDataManager
|
||||
* @throws IllegalStateException if server version is unsupported
|
||||
*/
|
||||
@NotNull PlayerManager getPlayerDataManager() {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.getPlayerManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the ISpecialPlayerInventory implementation for the given Player.
|
||||
*
|
||||
* @param player the Player
|
||||
* @return the ISpecialPlayerInventory created
|
||||
*/
|
||||
ISpecialPlayerInventory createInventory(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.createPlayerInventory(player);
|
||||
/**
|
||||
* Creates an instance of the ISpecialEnderChest implementation for the given Player.
|
||||
*
|
||||
* @param player the Player
|
||||
* @return the ISpecialEnderChest created
|
||||
*/
|
||||
ISpecialEnderChest createEnderChest(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.createEnderChest(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the ISpecialPlayerInventory implementation for the given Player.
|
||||
*
|
||||
* @param player the Player
|
||||
* @return the ISpecialPlayerInventory created
|
||||
*/
|
||||
ISpecialPlayerInventory createInventory(final Player player) {
|
||||
if (internal == null) {
|
||||
throw new IllegalStateException(String.format("Unsupported server version %s!", BukkitVersions.MINECRAFT));
|
||||
}
|
||||
return internal.createPlayerInventory(player);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import com.github.jikoo.planarwrappers.util.version.BukkitVersions;
|
||||
import com.github.jikoo.planarwrappers.util.version.Version;
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import com.lishid.openinv.OpenInv;
|
||||
import com.lishid.openinv.event.OpenEvents;
|
||||
@@ -19,8 +17,6 @@ import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -109,22 +105,29 @@ public class InventoryManager implements Listener {
|
||||
enderChests.computeIfPresent(uuid, this::remove);
|
||||
}
|
||||
|
||||
public void save(@NotNull UUID uuid) {
|
||||
consumeLoaded(uuid, inventory -> {});
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
|
||||
inventory.setPlayerOnline(event.getPlayer());
|
||||
checkViewerAccess(inventory, true);
|
||||
});
|
||||
consumeLoaded(
|
||||
event.getPlayer().getUniqueId(),
|
||||
inventory -> {
|
||||
inventory.setPlayerOnline(event.getPlayer());
|
||||
checkViewerAccess(inventory, true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Keep
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
consumeLoaded(event.getPlayer().getUniqueId(), inventory -> {
|
||||
inventory.setPlayerOffline();
|
||||
checkViewerAccess(inventory, false);
|
||||
});
|
||||
consumeLoaded(
|
||||
event.getPlayer().getUniqueId(),
|
||||
inventory -> checkViewerAccess(inventory, false)
|
||||
);
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -196,7 +199,8 @@ public class InventoryManager implements Listener {
|
||||
plugin.getLogger().log(
|
||||
Level.WARNING,
|
||||
"Prevented a plugin from opening an untracked ISpecialInventory!",
|
||||
new Throwable("Untracked ISpecialInventory"));
|
||||
new Throwable("Untracked ISpecialInventory")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,15 +212,6 @@ public class InventoryManager implements Listener {
|
||||
|
||||
// Copy viewers so we don't modify the list we're iterating over when closing inventories.
|
||||
List<HumanEntity> viewers = new ArrayList<>(inventory.getBukkitInventory().getViewers());
|
||||
// Legacy: Owner is always a viewer of own inventory.
|
||||
if (BukkitVersions.MINECRAFT.lessThan(Version.of(1, 21))) {
|
||||
if (inventory instanceof ISpecialPlayerInventory) {
|
||||
Inventory active = owner.getOpenInventory().getTopInventory();
|
||||
if (!active.equals(inventory.getBukkitInventory())) {
|
||||
viewers.remove(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (HumanEntity viewer : viewers) {
|
||||
if (alwaysDenied
|
||||
@@ -237,7 +232,8 @@ public class InventoryManager implements Listener {
|
||||
@NotNull Map<UUID, T> map,
|
||||
@NotNull UUID key,
|
||||
boolean saved,
|
||||
@NotNull Consumer<@NotNull ISpecialInventory> consumer) {
|
||||
@NotNull Consumer<@NotNull ISpecialInventory> consumer
|
||||
) {
|
||||
T inventory = map.get(key);
|
||||
|
||||
if (inventory == null) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -21,7 +20,6 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -42,7 +40,8 @@ public class PlayerLoader implements Listener {
|
||||
@NotNull Config config,
|
||||
@NotNull InventoryManager inventoryManager,
|
||||
@NotNull InternalAccessor internalAccessor,
|
||||
@NotNull Logger logger) {
|
||||
@NotNull Logger logger
|
||||
) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.inventoryManager = inventoryManager;
|
||||
@@ -93,7 +92,7 @@ public class PlayerLoader implements Listener {
|
||||
return player;
|
||||
}
|
||||
|
||||
public @Nullable OfflinePlayer match(@NotNull String name) {
|
||||
public @Nullable OfflinePlayer matchExact(@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()) {
|
||||
logger.warning("Call to PlayerSearchCache#matchPlayer made on the main thread!");
|
||||
@@ -143,6 +142,16 @@ public class PlayerLoader implements Listener {
|
||||
return player;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable OfflinePlayer match(@NotNull String name) {
|
||||
OfflinePlayer player = this.matchExact(name);
|
||||
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
// Inexact online match.
|
||||
player = Bukkit.getServer().getPlayer(name);
|
||||
|
||||
@@ -197,28 +206,31 @@ public class PlayerLoader implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getScheduler().runTaskLaterAsynchronously(() -> {
|
||||
Iterator<Map.Entry<String, PlayerProfile>> iterator = lookupCache.asMap().entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, PlayerProfile> entry = iterator.next();
|
||||
String oldMatch = entry.getValue().getName();
|
||||
plugin.getScheduler().runTaskLaterAsynchronously(
|
||||
() -> {
|
||||
Iterator<Map.Entry<String, PlayerProfile>> iterator = lookupCache.asMap().entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, PlayerProfile> entry = iterator.next();
|
||||
String oldMatch = entry.getValue().getName();
|
||||
|
||||
// Shouldn't be possible - all profiles should be complete.
|
||||
if (oldMatch == null) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
// Shouldn't be possible - all profiles should be complete.
|
||||
if (oldMatch == null) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
String lookup = entry.getKey();
|
||||
float oldMatchScore = StringMetric.compareJaroWinkler(lookup, oldMatch);
|
||||
float newMatchScore = StringMetric.compareJaroWinkler(lookup, name);
|
||||
String lookup = entry.getKey();
|
||||
float oldMatchScore = StringMetric.compareJaroWinkler(lookup, oldMatch);
|
||||
float newMatchScore = StringMetric.compareJaroWinkler(lookup, name);
|
||||
|
||||
// If new match exceeds old match, delete old match.
|
||||
if (newMatchScore > oldMatchScore) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}, 7L);
|
||||
// If new match exceeds old match, delete old match.
|
||||
if (newMatchScore > oldMatchScore) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
7L
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.lishid.openinv.util;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||
import org.bukkit.inventory.meta.BundleMeta;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class SearchHelper {
|
||||
|
||||
public static boolean findMatch(@NotNull Inventory inventory, @NotNull Predicate<@NotNull ItemStack> predicate) {
|
||||
for (ItemStack content : inventory.getContents()) {
|
||||
if (findMatch(content, predicate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean findMatch(@Nullable ItemStack itemStack, @NotNull Predicate<@NotNull ItemStack> predicate) {
|
||||
if (itemStack == null || itemStack.getType().isAir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the item is the search target, done.
|
||||
if (predicate.test(itemStack)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the item doesn't have meta, it cannot contain items.
|
||||
if (!itemStack.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
|
||||
// Container meta with items (primarily shulkers).
|
||||
if (meta instanceof BlockStateMeta stateMeta) {
|
||||
if (!stateMeta.hasBlockState() || !(stateMeta.getBlockState() instanceof InventoryHolder holder)) {
|
||||
return false;
|
||||
}
|
||||
Inventory inventory = holder.getInventory();
|
||||
return findMatch(inventory, predicate);
|
||||
}
|
||||
|
||||
// Bundle meta.
|
||||
if (meta instanceof BundleMeta bundleMeta) {
|
||||
for (ItemStack subStack : bundleMeta.getItems()) {
|
||||
if (findMatch(subStack, predicate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private SearchHelper() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
}
|
||||
@@ -23,133 +23,142 @@ package com.lishid.openinv.util;
|
||||
|
||||
public class StringMetric {
|
||||
|
||||
public static float compareJaroWinkler(String a, String b) {
|
||||
final float jaroScore = compareJaro(a, b);
|
||||
|
||||
if (jaroScore < (float) 0.7) {
|
||||
return jaroScore;
|
||||
}
|
||||
|
||||
String prefix = commonPrefix(a, b);
|
||||
int prefixLength = Math.min(prefix.codePointCount(0, prefix.length()), 4);
|
||||
|
||||
return jaroScore + (prefixLength * (float) 0.1 * (1.0f - jaroScore));
|
||||
public static float compareJaroWinkler(String a, String b) {
|
||||
final float jaroScore = compareJaro(a, b);
|
||||
|
||||
if (jaroScore < (float) 0.7) {
|
||||
return jaroScore;
|
||||
}
|
||||
|
||||
private static float compareJaro(String a, String b) {
|
||||
if (a.isEmpty() && b.isEmpty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
String prefix = commonPrefix(a, b);
|
||||
int prefixLength = Math.min(prefix.codePointCount(0, prefix.length()), 4);
|
||||
|
||||
if (a.isEmpty() || b.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
return jaroScore + (prefixLength * (float) 0.1 * (1.0f - jaroScore));
|
||||
|
||||
final int[] charsA = a.codePoints().toArray();
|
||||
final int[] charsB = b.codePoints().toArray();
|
||||
}
|
||||
|
||||
// Intentional integer division to round down.
|
||||
final int halfLength = Math.max(0, Math.max(charsA.length, charsB.length) / 2 - 1);
|
||||
|
||||
final int[] commonA = getCommonCodePoints(charsA, charsB, halfLength);
|
||||
final int[] commonB = getCommonCodePoints(charsB, charsA, halfLength);
|
||||
|
||||
// commonA and commonB will always contain the same multi-set of
|
||||
// characters. Because getCommonCharacters has been optimized, commonA
|
||||
// and commonB are -1-padded. So in this loop we count transposition
|
||||
// and use commonCharacters to determine the length of the multi-set.
|
||||
float transpositions = 0;
|
||||
int commonCharacters = 0;
|
||||
for (int length = commonA.length; commonCharacters < length
|
||||
&& commonA[commonCharacters] > -1; commonCharacters++) {
|
||||
if (commonA[commonCharacters] != commonB[commonCharacters]) {
|
||||
transpositions++;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonCharacters == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float aCommonRatio = commonCharacters / (float) charsA.length;
|
||||
float bCommonRatio = commonCharacters / (float) charsB.length;
|
||||
float transpositionRatio = (commonCharacters - transpositions / 2.0f) / commonCharacters;
|
||||
|
||||
return (aCommonRatio + bCommonRatio + transpositionRatio) / 3.0f;
|
||||
private static float compareJaro(String a, String b) {
|
||||
if (a.isEmpty() && b.isEmpty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an array of code points from a within b. A character in b is
|
||||
* counted as common when it is within separation distance from the position
|
||||
* in a.
|
||||
*/
|
||||
private static int[] getCommonCodePoints(final int[] charsA, final int[] charsB, final int separation) {
|
||||
final int[] common = new int[Math.min(charsA.length, charsB.length)];
|
||||
final boolean[] matched = new boolean[charsB.length];
|
||||
|
||||
// Iterate of string a and find all characters that occur in b within
|
||||
// the separation distance. Mark any matches found to avoid
|
||||
// duplicate matchings.
|
||||
int commonIndex = 0;
|
||||
for (int i = 0, length = charsA.length; i < length; i++) {
|
||||
final int character = charsA[i];
|
||||
final int index = indexOf(character, charsB, i - separation, i
|
||||
+ separation + 1, matched);
|
||||
if (index > -1) {
|
||||
common[commonIndex++] = character;
|
||||
matched[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonIndex < common.length) {
|
||||
common[commonIndex] = -1;
|
||||
}
|
||||
|
||||
// Both invocations will yield the same multi-set terminated by -1, so
|
||||
// they can be compared for transposition without making a copy.
|
||||
return common;
|
||||
if (a.isEmpty() || b.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for code point in buffer starting at fromIndex to toIndex - 1.
|
||||
*
|
||||
* Returns -1 when not found.
|
||||
*/
|
||||
private static int indexOf(int character, int[] buffer, int fromIndex, int toIndex, boolean[] matched) {
|
||||
final int[] charsA = a.codePoints().toArray();
|
||||
final int[] charsB = b.codePoints().toArray();
|
||||
|
||||
// compare char with range of characters to either side
|
||||
for (int j = Math.max(0, fromIndex), length = Math.min(toIndex, buffer.length); j < length; j++) {
|
||||
// check if found
|
||||
if (buffer[j] == character && !matched[j]) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
// Intentional integer division to round down.
|
||||
final int halfLength = Math.max(0, Math.max(charsA.length, charsB.length) / 2 - 1);
|
||||
|
||||
return -1;
|
||||
final int[] commonA = getCommonCodePoints(charsA, charsB, halfLength);
|
||||
final int[] commonB = getCommonCodePoints(charsB, charsA, halfLength);
|
||||
|
||||
// commonA and commonB will always contain the same multi-set of
|
||||
// characters. Because getCommonCharacters has been optimized, commonA
|
||||
// and commonB are -1-padded. So in this loop we count transposition
|
||||
// and use commonCharacters to determine the length of the multi-set.
|
||||
float transpositions = 0;
|
||||
int commonCharacters = 0;
|
||||
for (
|
||||
int length = commonA.length;
|
||||
commonCharacters < length && commonA[commonCharacters] > -1;
|
||||
commonCharacters++
|
||||
) {
|
||||
if (commonA[commonCharacters] != commonB[commonCharacters]) {
|
||||
transpositions++;
|
||||
}
|
||||
}
|
||||
|
||||
private static String commonPrefix(CharSequence a, CharSequence b) {
|
||||
int maxPrefixLength = Math.min(a.length(), b.length());
|
||||
|
||||
int p;
|
||||
|
||||
p = 0;
|
||||
while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) {
|
||||
++p;
|
||||
}
|
||||
|
||||
if (validSurrogatePairAt(a, p - 1) || validSurrogatePairAt(b, p - 1)) {
|
||||
--p;
|
||||
}
|
||||
|
||||
return a.subSequence(0, p).toString();
|
||||
if (commonCharacters == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private static boolean validSurrogatePairAt(CharSequence string, int index) {
|
||||
return index >= 0 && index <= string.length() - 2 && Character.isHighSurrogate(string.charAt(index)) && Character.isLowSurrogate(string.charAt(index + 1));
|
||||
float aCommonRatio = commonCharacters / (float) charsA.length;
|
||||
float bCommonRatio = commonCharacters / (float) charsB.length;
|
||||
float transpositionRatio = (commonCharacters - transpositions / 2.0f) / commonCharacters;
|
||||
|
||||
return (aCommonRatio + bCommonRatio + transpositionRatio) / 3.0f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an array of code points from a within b. A character in b is
|
||||
* counted as common when it is within separation distance from the position
|
||||
* in a.
|
||||
*/
|
||||
private static int[] getCommonCodePoints(final int[] charsA, final int[] charsB, final int separation) {
|
||||
final int[] common = new int[Math.min(charsA.length, charsB.length)];
|
||||
final boolean[] matched = new boolean[charsB.length];
|
||||
|
||||
// Iterate of string a and find all characters that occur in b within
|
||||
// the separation distance. Mark any matches found to avoid
|
||||
// duplicate matchings.
|
||||
int commonIndex = 0;
|
||||
for (int i = 0, length = charsA.length; i < length; i++) {
|
||||
final int character = charsA[i];
|
||||
final int index = indexOf(
|
||||
character,
|
||||
charsB,
|
||||
i - separation,
|
||||
i + separation + 1,
|
||||
matched
|
||||
);
|
||||
if (index > -1) {
|
||||
common[commonIndex++] = character;
|
||||
matched[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private StringMetric(){}
|
||||
if (commonIndex < common.length) {
|
||||
common[commonIndex] = -1;
|
||||
}
|
||||
|
||||
// Both invocations will yield the same multi-set terminated by -1, so
|
||||
// they can be compared for transposition without making a copy.
|
||||
return common;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for code point in buffer starting at fromIndex to toIndex - 1.
|
||||
*
|
||||
* Returns -1 when not found.
|
||||
*/
|
||||
private static int indexOf(int character, int[] buffer, int fromIndex, int toIndex, boolean[] matched) {
|
||||
|
||||
// compare char with range of characters to either side
|
||||
for (int j = Math.max(0, fromIndex), length = Math.min(toIndex, buffer.length); j < length; j++) {
|
||||
// check if found
|
||||
if (buffer[j] == character && !matched[j]) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String commonPrefix(CharSequence a, CharSequence b) {
|
||||
int maxPrefixLength = Math.min(a.length(), b.length());
|
||||
|
||||
int p;
|
||||
|
||||
p = 0;
|
||||
while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) {
|
||||
++p;
|
||||
}
|
||||
|
||||
if (validSurrogatePairAt(a, p - 1) || validSurrogatePairAt(b, p - 1)) {
|
||||
--p;
|
||||
}
|
||||
|
||||
return a.subSequence(0, p).toString();
|
||||
}
|
||||
|
||||
private static boolean validSurrogatePairAt(CharSequence string, int index) {
|
||||
return index >= 0 && index <= string.length() - 2 && Character.isHighSurrogate(string.charAt(index)) && Character.isLowSurrogate(string.charAt(index + 1));
|
||||
}
|
||||
|
||||
private StringMetric() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -32,119 +35,132 @@ import java.util.function.Function;
|
||||
*/
|
||||
public final class TabCompleter {
|
||||
|
||||
/**
|
||||
* Offer tab completions for whole numbers.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @return integer options
|
||||
*/
|
||||
public static List<String> completeInteger(String argument) {
|
||||
// Ensure existing argument is actually a number
|
||||
if (!argument.isEmpty()) {
|
||||
try {
|
||||
Integer.parseInt(argument);
|
||||
} catch (NumberFormatException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
List<String> completions = new ArrayList<>(10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
completions.add(argument + i);
|
||||
}
|
||||
|
||||
return completions;
|
||||
/**
|
||||
* Offer tab completions for whole numbers.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @return integer options
|
||||
*/
|
||||
public static @NotNull @Unmodifiable List<String> completeInteger(@NotNull String argument) {
|
||||
// Ensure existing argument is actually a number
|
||||
if (!argument.isEmpty()) {
|
||||
try {
|
||||
Integer.parseInt(argument);
|
||||
} catch (NumberFormatException e) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer tab completions for a given Enum.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param enumClazz the Enum to complete for
|
||||
* @return the matching Enum values
|
||||
*/
|
||||
public static List<String> completeEnum(String argument, Class<? extends Enum<?>> enumClazz) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
for (Enum<?> enumConstant : enumClazz.getEnumConstants()) {
|
||||
String name = enumConstant.name().toLowerCase(Locale.ENGLISH);
|
||||
if (name.startsWith(argument)) {
|
||||
completions.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
List<String> completions = new ArrayList<>(10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
completions.add(argument + i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer tab completions for a given array of Strings.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param options the Strings which may be completed
|
||||
* @return the matching Strings
|
||||
*/
|
||||
public static List<String> completeString(String argument, String[] options) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
return Collections.unmodifiableList(completions);
|
||||
}
|
||||
|
||||
for (String option : options) {
|
||||
if (option.startsWith(argument)) {
|
||||
completions.add(option);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Offer tab completions for a given Enum.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param enumClazz the Enum to complete for
|
||||
* @return the matching Enum values
|
||||
*/
|
||||
public static @NotNull List<String> completeEnum(
|
||||
@NotNull String argument,
|
||||
@NotNull Class<? extends Enum<?>> enumClazz
|
||||
) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
return completions;
|
||||
for (Enum<?> enumConstant : enumClazz.getEnumConstants()) {
|
||||
String name = enumConstant.name().toLowerCase(Locale.ENGLISH);
|
||||
if (name.startsWith(argument)) {
|
||||
completions.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer tab completions for visible online Players' names.
|
||||
*
|
||||
* @param sender the command's sender
|
||||
* @param argument the argument to complete
|
||||
* @return the matching Players' names
|
||||
*/
|
||||
public static List<String> completeOnlinePlayer(CommandSender sender, String argument) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
Player senderPlayer = sender instanceof Player ? (Player) sender : null;
|
||||
return completions;
|
||||
}
|
||||
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (senderPlayer != null && !senderPlayer.canSee(player)) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Offer tab completions for a given array of Strings.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param options the Strings which may be completed
|
||||
* @return the matching Strings
|
||||
*/
|
||||
public static @NotNull List<String> completeString(
|
||||
@NotNull String argument,
|
||||
@NotNull String @NotNull [] options
|
||||
) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (StringUtil.startsWithIgnoreCase(player.getName(), argument)) {
|
||||
completions.add(player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
for (String option : options) {
|
||||
if (option.startsWith(argument)) {
|
||||
completions.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer tab completions for a given array of Objects.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param converter the Function for converting the Object into a comparable String
|
||||
* @param options the Objects which may be completed
|
||||
* @return the matching Strings
|
||||
*/
|
||||
public static <T> List<String> completeObject(String argument, Function<T, String> converter, T[] options) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
return completions;
|
||||
}
|
||||
|
||||
for (T option : options) {
|
||||
String optionString = converter.apply(option).toLowerCase(Locale.ENGLISH);
|
||||
if (optionString.startsWith(argument)) {
|
||||
completions.add(optionString);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Offer tab completions for visible online Players' names.
|
||||
*
|
||||
* @param sender the command's sender
|
||||
* @param argument the argument to complete
|
||||
* @return the matching Players' names
|
||||
*/
|
||||
public static List<String> completeOnlinePlayer(
|
||||
@Nullable CommandSender sender,
|
||||
@NotNull String argument
|
||||
) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
Player senderPlayer = sender instanceof Player player ? player : null;
|
||||
|
||||
return completions;
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (senderPlayer != null && !senderPlayer.canSee(player)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (StringUtil.startsWithIgnoreCase(player.getName(), argument)) {
|
||||
completions.add(player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private TabCompleter() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
return completions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer tab completions for a given array of Objects.
|
||||
*
|
||||
* @param argument the argument to complete
|
||||
* @param converter the Function for converting the Object into a comparable String
|
||||
* @param options the Objects which may be completed
|
||||
* @return the matching Strings
|
||||
*/
|
||||
public static <T> List<String> completeObject(
|
||||
@NotNull String argument,
|
||||
@NotNull Function<@NotNull T, @NotNull String> converter,
|
||||
@NotNull T @NotNull[] options
|
||||
) {
|
||||
argument = argument.toLowerCase(Locale.ENGLISH);
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
for (T option : options) {
|
||||
String optionString = converter.apply(option).toLowerCase(Locale.ENGLISH);
|
||||
if (optionString.startsWith(argument)) {
|
||||
completions.add(optionString);
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
private TabCompleter() {
|
||||
throw new IllegalStateException("Cannot create instance of utility class.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,104 +26,104 @@ import java.io.IOException;
|
||||
|
||||
public record ConfigUpdater(@NotNull Plugin plugin) {
|
||||
|
||||
public void checkForUpdates() {
|
||||
final int version = plugin.getConfig().getInt("config-version", 1);
|
||||
ConfigurationSection defaults = plugin.getConfig().getDefaults();
|
||||
if (defaults == null || version >= defaults.getInt("config-version")) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Configuration update found! Performing update...");
|
||||
|
||||
// Backup the old config file
|
||||
try {
|
||||
plugin.getConfig().save(new File(plugin.getDataFolder(), "config_old.yml"));
|
||||
plugin.getLogger().info("Backed up config.yml to config_old.yml before updating.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Could not back up config.yml before updating!");
|
||||
}
|
||||
|
||||
if (version < 2) {
|
||||
updateConfig1To2();
|
||||
}
|
||||
if (version < 3) {
|
||||
updateConfig2To3();
|
||||
}
|
||||
if (version < 4) {
|
||||
updateConfig3To4();
|
||||
}
|
||||
if (version < 5) {
|
||||
updateConfig4To5();
|
||||
}
|
||||
if (version < 6) {
|
||||
updateConfig5To6();
|
||||
}
|
||||
if (version < 7) {
|
||||
updateConfig6To7();
|
||||
}
|
||||
if (version < 8) {
|
||||
updateConfig7To8();
|
||||
}
|
||||
|
||||
plugin.saveConfig();
|
||||
plugin.getLogger().info("Configuration update complete!");
|
||||
public void checkForUpdates() {
|
||||
final int version = plugin.getConfig().getInt("config-version", 1);
|
||||
ConfigurationSection defaults = plugin.getConfig().getDefaults();
|
||||
if (defaults == null || version >= defaults.getInt("config-version")) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void updateConfig7To8() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.equal-access", "view");
|
||||
config.set("config-version", 8);
|
||||
plugin.getLogger().info("Configuration update found! Performing update...");
|
||||
|
||||
// Backup the old config file
|
||||
try {
|
||||
plugin.getConfig().save(new File(plugin.getDataFolder(), "config_old.yml"));
|
||||
plugin.getLogger().info("Backed up config.yml to config_old.yml before updating.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Could not back up config.yml before updating!");
|
||||
}
|
||||
|
||||
private void updateConfig6To7() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("toggles", null);
|
||||
String consoleLocale = config.getString("settings.locale", "en");
|
||||
if (consoleLocale.isBlank() || consoleLocale.equalsIgnoreCase("en_us")) {
|
||||
consoleLocale = "en";
|
||||
}
|
||||
config.set("settings.console-locale", consoleLocale);
|
||||
config.set("settings.locale", null);
|
||||
config.set("config-version", 7);
|
||||
if (version < 2) {
|
||||
updateConfig1To2();
|
||||
}
|
||||
if (version < 3) {
|
||||
updateConfig2To3();
|
||||
}
|
||||
if (version < 4) {
|
||||
updateConfig3To4();
|
||||
}
|
||||
if (version < 5) {
|
||||
updateConfig4To5();
|
||||
}
|
||||
if (version < 6) {
|
||||
updateConfig5To6();
|
||||
}
|
||||
if (version < 7) {
|
||||
updateConfig6To7();
|
||||
}
|
||||
if (version < 8) {
|
||||
updateConfig7To8();
|
||||
}
|
||||
|
||||
private void updateConfig5To6() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.command.open.no-args-opens-self", false);
|
||||
config.set("settings.command.searchcontainer.max-radius", 10);
|
||||
config.set("config-version", 6);
|
||||
}
|
||||
plugin.saveConfig();
|
||||
plugin.getLogger().info("Configuration update complete!");
|
||||
}
|
||||
|
||||
private void updateConfig4To5() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.disable-offline-access", false);
|
||||
config.set("config-version", 5);
|
||||
}
|
||||
private void updateConfig7To8() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.equal-access", "view");
|
||||
config.set("config-version", 8);
|
||||
}
|
||||
|
||||
private void updateConfig3To4() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("notify", null);
|
||||
config.set("config-version", 4);
|
||||
private void updateConfig6To7() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("toggles", null);
|
||||
String consoleLocale = config.getString("settings.locale", "en");
|
||||
if (consoleLocale.isBlank() || consoleLocale.equalsIgnoreCase("en_us")) {
|
||||
consoleLocale = "en";
|
||||
}
|
||||
config.set("settings.console-locale", consoleLocale);
|
||||
config.set("settings.locale", null);
|
||||
config.set("config-version", 7);
|
||||
}
|
||||
|
||||
private void updateConfig2To3() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("items", null);
|
||||
config.set("ItemOpenInv", null);
|
||||
config.set("toggles", null);
|
||||
config.set("settings.disable-saving", config.getBoolean("DisableSaving", false));
|
||||
config.set("DisableSaving", null);
|
||||
config.set("config-version", 3);
|
||||
}
|
||||
private void updateConfig5To6() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.command.open.no-args-opens-self", false);
|
||||
config.set("settings.command.searchcontainer.max-radius", 10);
|
||||
config.set("config-version", 6);
|
||||
}
|
||||
|
||||
private void updateConfig1To2() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("ItemOpenInvItemID", null);
|
||||
config.set("NotifySilentChest", null);
|
||||
config.set("NotifyAnyChest", null);
|
||||
config.set("AnyChest", null);
|
||||
config.set("SilentChest", null);
|
||||
config.set("config-version", 2);
|
||||
}
|
||||
private void updateConfig4To5() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("settings.disable-offline-access", false);
|
||||
config.set("config-version", 5);
|
||||
}
|
||||
|
||||
private void updateConfig3To4() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("notify", null);
|
||||
config.set("config-version", 4);
|
||||
}
|
||||
|
||||
private void updateConfig2To3() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("items", null);
|
||||
config.set("ItemOpenInv", null);
|
||||
config.set("toggles", null);
|
||||
config.set("settings.disable-saving", config.getBoolean("DisableSaving", false));
|
||||
config.set("DisableSaving", null);
|
||||
config.set("config-version", 3);
|
||||
}
|
||||
|
||||
private void updateConfig1To2() {
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
config.set("ItemOpenInvItemID", null);
|
||||
config.set("NotifySilentChest", null);
|
||||
config.set("NotifyAnyChest", null);
|
||||
config.set("AnyChest", null);
|
||||
config.set("SilentChest", null);
|
||||
config.set("config-version", 2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,3 @@ messages:
|
||||
matches: 'Container hat %target%: %detail%'
|
||||
'on': 'an'
|
||||
'off': 'aus'
|
||||
container:
|
||||
player: '%player%''s Inventar'
|
||||
enderchest: '%player%''s Endertruhe'
|
||||
@@ -16,6 +16,9 @@ messages:
|
||||
containerBlocked: 'You are opening a blocked container.'
|
||||
containerBlockedSilent: 'You are opening a blocked container silently.'
|
||||
containerSilent: 'You are opening a container silently.'
|
||||
clear:
|
||||
inventory: 'Cleared %target%''s inventory.'
|
||||
enderchest: 'Cleared %target%''s ender chest.'
|
||||
settingState: '%setting%: %state%'
|
||||
player:
|
||||
noMatches: 'No players found with %target%.'
|
||||
@@ -25,6 +28,3 @@ messages:
|
||||
matches: 'Containers holding %target%: %detail%'
|
||||
'on': 'on'
|
||||
'off': 'off'
|
||||
container:
|
||||
player: '%player%''s Inventory'
|
||||
enderchest: '%player%''s Ender Chest'
|
||||
@@ -25,7 +25,3 @@ messages:
|
||||
matches: 'Contenedores con %target%: %detail%'
|
||||
'on': 'activado'
|
||||
'off': 'desactivado'
|
||||
container:
|
||||
player: 'Inventario de %player%'
|
||||
enderchest: 'Cofre de Ender de %player%'
|
||||
|
||||
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user