Compare commits

..
21 Commits
Author SHA1 Message Date
Jikoo 7256494df3 Fix inventories of players on their first login not being openable
Closes #7
2016-04-18 13:05:56 -04:00
Jikoo 1fbaa5b2a9 Reduce redundant saves for ender chests as well
While they don't suffer the same owner-related issues that player inventories did, there's no need to save so many times.
2016-04-11 18:45:23 -04:00
Jikoo eeb28b4210 Reduce duplicate saves and fix for owner coming online for all versions 2016-04-11 18:12:21 -04:00
Jikoo 0ebe6fe132 Properly update PlayerInventory owner when they come online
Closes #5
2016-04-11 17:59:28 -04:00
Jikoo e39ab10797 Don't load from player.dat for online players
Fixes lishid#40 and #5
2016-04-11 15:44:25 -04:00
Jikoo fb48f6a783 Fix up legacy code to use new interfaces
This took way more time than it was worth.
Converted line endings to Unix, some other trivial crap.
Thanks to Phoenix616 for already having a multi-version pom, it would have taken me much longer to figure out without a functional example.

The only legacy code which is not supported is that of the pre-versioned package era. I compiled a copy of Craftbukkit about 15 commits prior to the introduction of versioned packages, and NMS had already been fiddled with. Anyone who needs support for builds in that range can use an older OpenInv build instead, it's not like UUIDs are a big concern.
2016-03-04 05:49:07 -05:00
Jikoo d761b6bfe4 Actually allow for offhand modification, whoops. 2016-03-04 00:53:21 -05:00
Jikoo 5b3ba79b82 Maven, huge refactor, interface changes
Among other things, these interface changes allow for supporting pre-UUID Minecraft versions again. I may eventually backport older versions, but I don't know that I'll be able to lay hands on old enough jars.
Removed static access
Renamed all methods to follow conventions
2016-03-03 23:33:54 -05:00
Jikoo 3549431fbc Removed item wand functionality
Anyone who can use it has to be able to run the command anyway. If they can run the command, they can use tab completion to automatically fill in anyone online's name. There's no need to keep around a bunch of deprecated code for a function I sincerely doubt anyone actually uses.
2016-03-01 12:30:12 -05:00
Jikoo 17b0cb6efe Fix players without permission to modify inventories inserting items 2016-03-01 12:22:26 -05:00
Jikoo 2d7522554c Use AnyChest when an ocelot is on a chest.
Closes #2
2016-03-01 12:09:29 -05:00
Jikoo 131f874329 Added offhand slot display and manipulation 2016-03-01 11:44:38 -05:00
Jikoo a623af5119 1.9.0 2016-03-01 00:52:57 -05:00
Jikoo 8f17bd0237 Remove update checker and bumped version for release
Added myself to the authors list as I've been maintaining this fork for a while
2015-11-16 23:21:34 -05:00
Jikoo 20fb61705d Update to 1.8.8, properly declare permissions in plugin.yml 2015-11-16 23:12:46 -05:00
Jikoo c0f513177a Removed additional async lookup notification, current is pretty fast. 2015-05-19 08:11:53 -04:00
Jikoo b3a3947b03 1.8.4
The easiest NMS update ever.
2015-05-19 08:10:39 -04:00
Jikoo c7bab7d451 Whoops, didn't make sure my autoformatter's settings matched the style. 2015-03-12 16:42:00 -04:00
Jikoo 4058463f7f OpenInv 2.2.7
Updated for Spigot 1.8.3
Dropped support for all other versions
Fixed lookup not working for players on first login/incomplete names
2015-03-11 16:48:21 -04:00
Jikoo 21cd1926ae Don't find username on the main thread. Not friendly with partial names. 2014-12-01 16:11:52 -05:00
Jikoo 2a66b2e81b Updated to Spigot 1.8 2014-12-01 14:10:14 -05:00
327 changed files with 12220 additions and 18279 deletions

No files matched your search

-491
View File
@@ -1,491 +0,0 @@
[*]
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
-11
View File
@@ -1,11 +0,0 @@
# 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
-19
View File
@@ -1,19 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "monthly"
ignore:
# The SQLite JDBC is provided by Spigot and, for now, Paper.
# To ensure it is still present even if Paper stops providing it,
# we need to declare it as a library.
# To prevent needlessly downloading (and loading) a second copy,
# we want to ensure that we are using the provided version.
- dependency-name: "org.xerial:sqlite-jdbc"
# Spigot and Spigot API updates are manual.
- dependency-name: "org.spigotmc:*"
@@ -1,55 +0,0 @@
name: Auto-merge Dependabot PRs
on:
workflow_run:
workflows: [ "Pull Request" ]
types: [ completed ]
jobs:
merge-dependabot:
if: "github.actor == 'dependabot[bot]'
&& github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'"
runs-on: ubuntu-latest
steps:
# Note: this is directly from GitHub's example for using data from a triggering workflow:
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
- name: 'Download artifact'
uses: actions/github-script@v9
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr_number"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));
# This might be a useless use of cat, but I'm not sure what shell Actions is going to be running.
- name: Add Pull Number Variable
run: |-
unzip pr_number.zip
echo "PR_NUMBER=$(cat pr_number)" >> "$GITHUB_ENV"
- name: Approve
uses: hmarr/auto-approve-action@v4.0.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
pull-request-number: "${{ env.PR_NUMBER }}"
- name: Merge
uses: pascalgn/automerge-action@v0.16.4
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: "dependencies,java"
MERGE_METHOD: "squash"
PULL_REQUEST: "${{ env.PR_NUMBER }}"
-36
View File
@@ -1,36 +0,0 @@
name: OpenInv CI
on:
push:
branches:
- 'master'
tags-ignore:
- '**'
paths-ignore:
- resource-pack/openinv-legibility-pack/**
# Enable running CI via other Actions, i.e. for drafting releases and handling PRs.
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '25'
- uses: gradle/actions/setup-gradle@v5
- name: Build with Gradle
run: ./gradlew build
# Upload artifacts
- name: Upload Distributable Jar
id: upload-final
uses: actions/upload-artifact@v7
with:
name: dist
path: ./dist/*
-38
View File
@@ -1,38 +0,0 @@
name: Draft Github Release
on:
push:
tags:
- '**'
jobs:
run-ci:
uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
draft-release:
needs: [ run-ci ]
runs-on: ubuntu-latest
steps:
- name: Download build
uses: actions/download-artifact@v8
with:
name: dist
path: dist
- name: Create Release
id: create-release
uses: softprops/action-gh-release@v3.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: "OpenInv ${{ github.ref_name }}"
body: |-
## Supported server versions
**Paper:** TODO VERSION, 26.1.2, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6
**Spigot:** TODO VERSION
TODO HELLO HUMAN, PRESS THE GENERATE CHANGELOG BUTTON PLEASE.
Don't forget to add a newline after the resulting header and prettify the tag compare link!
draft: true
prerelease: false
files: ./dist/**
-110
View File
@@ -1,110 +0,0 @@
name: Release to external sites
on:
release:
types: [ released ]
jobs:
curseforge_release:
runs-on: ubuntu-latest
steps:
- &parse-paper-versions
name: Parse Paper Versions
id: parse_versions
run: |
# Find line declaring Paper versions.
raw=$(grep "\\*\\*Paper:\\*\\*" <<< "${{ github.event.release.body }}")
# Enable extended glob pattern to match 0 or more whitespace characters.
shopt -s extglob
# Trim Paper versions identifier prefix.
raw=${raw/*([[:space:]])'**'Paper:'**'*([[:space:]])/}
# Replace commas and optional spaces with a newline.
raw=${raw//,*([[:space:]])/$'\n'}
# Turn extglob back off.
shopt -u extglob
echo "$raw"
printf "PAPER_VERSIONS<<EOF\n%s\nEOF\n" "$raw" >> "$GITHUB_OUTPUT"
- &fetch-release-asset
name: Fetch Github Release Asset
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ github.event.release.id }}
file: OpenInv.jar
- name: Create CurseForge Release
uses: Kira-NT/mc-publish@v3.3
with:
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
curseforge-id: 31432
files: ./OpenInv.jar
game-versions: "${{ steps.parse_versions.outputs.PAPER_VERSIONS }}"
game-version-filter: releases
loaders: |
spigot
paper
folia
modrinth_release:
runs-on: ubuntu-latest
steps:
- *parse-paper-versions
- *fetch-release-asset
- name: Create Modrinth Release
uses: Kira-NT/mc-publish@v3.3
with:
modrinth-id: 1UlvXbzL
modrinth-featured: false
modrinth-unfeature-mode: none
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: ./OpenInv.jar
game-versions: "${{ steps.parse_versions.outputs.PAPER_VERSIONS }}"
game-version-filter: releases
loaders: |
spigot
paper
folia
hangar_release:
runs-on: ubuntu-latest
steps:
- name: Parse Hangar Paper Versions
id: parse_versions
run: |
# Find line declaring Paper versions.
raw=$(grep "\\*\\*Paper:\\*\\*" <<< "${{ github.event.release.body }}")
# Trim Paper versions identifier prefix.
raw="${raw/'**'Paper:'**'/}"
# Trim leading and trailing spaces.
raw="${raw##[[:space:]]}"
raw="${raw%%[[:space:]]}"
# Generate JSON for Hangar. GitHub Actions provides jq 1.7 in the 24.04 image.
# Split on comma and optional spaces.
hangar_versions=$(jq -nc --arg versions "$raw" '{"PAPER": $versions | split(", *";null)}')
echo "$hangar_versions"
echo "HANGAR_PAPER_VERSIONS=$hangar_versions" >> "$GITHUB_OUTPUT"
- *fetch-release-asset
- name: Create Hangar Release
uses: benwoo1110/hangar-upload-action@v1
with:
api_token: ${{ secrets.HANGAR_TOKEN }}
slug: 'OpenInv'
version: ${{ github.event.release.tag_name }}
channel: release
description: "${{ github.event.release.body }}"
files: |-
[
{
"path": "./OpenInv.jar",
"platforms": ["PAPER"]
}
]
platform_dependencies: "${{ steps.parse_versions.outputs.HANGAR_PAPER_VERSIONS }}"
-22
View File
@@ -1,22 +0,0 @@
name: Pull Request
on:
pull_request:
jobs:
run-ci:
uses: Jikoo/OpenInv/.github/workflows/ci.yml@master
store-dependabot-pr-data:
if: "github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'"
runs-on: ubuntu-latest
steps:
# Note: this is directly from GitHub's example for using data from a triggering workflow:
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
- name: Store Pull Number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/pr_number
- uses: actions/upload-artifact@v7
with:
name: pr_number
path: pr/
-24
View File
@@ -1,24 +0,0 @@
name: Resource Pack CI
on:
push:
branches:
- 'master'
tags-ignore:
- '**'
paths:
- resource-pack/openinv-legibility-pack/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Build resource pack
id: upload-resource-pack
uses: actions/upload-artifact@v7
with:
name: openinv-legibility-pack
path: ./resource-pack/openinv-legibility-pack/
compression-level: 9
-14
View File
@@ -1,14 +0,0 @@
**/.settings
**/.project
**/.classpath
**/.idea/
**/.gradle/
**.iml
**/target/
**/bin/
**/out/
**/build/
**/dist/
**/dependency-reduced-pom.xml
**/pom.xml.versionsBackup
**/.vscode/
+26
View File
@@ -0,0 +1,26 @@
## Goals
This fork of OpenInv was created when the lag caused by offline player lookups post-UUID migration became too much to bear. Overall, it does not differ much from the original.
### Changes of Note
- Removed updater
- Removed wand
## Previous Versions
Any version of Minecraft this fork has been existent for should have a [release](https://github.com/Jikoo/OpenInv/releases) tagged. Features are not backported, however.
## License
```
Copyright (C) 2011-2014 lishid. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
-65
View File
@@ -1,65 +0,0 @@
## About
OpenInv is a Bukkit plugin which allows users to open and edit anyone's
inventory or ender chest - online or not!
Downloads are available on [GitHub](https://github.com/Jikoo/OpenInv/releases), [DBO](https://dev.bukkit.org/projects/openinv)/[CurseForge](https://www.curseforge.com/minecraft/bukkit-plugins/openinv), [Modrinth](https://modrinth.com/plugin/openinv), and [Hangar](https://hangar.papermc.io/jikoo/OpenInv).
## 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.
- No duplicate slots! Only armor is accessible when opening self (if allowed at all)!
- Drop items as the player! Place items in the dropper slot in the bottom right. Can be disabled via permission!
- Allow any item in armor slots! Configurable via permission.
- **OpenEnder**: Open anyone's ender chest, even if they're offline.
- Allow access only to own ender chest! Don't grant permission to open others.
- Read-only mode! Don't grant edit permission.
- Cross-world support! Allow access only from the same world.
- **SilentContainer**: Open containers without displaying an animation or making sound.
- **AnyContainer**: Open containers, even if blocked by ocelots or blocks.
- **Resource Pack Support**: Personalize view using resource pack tricks like custom fonts and characters. See [the wiki](https://github.com/Jikoo/OpenInv/wiki/Customizing-Inventories) for details.
## 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>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>com.github.Jikoo</groupId>
<artifactId>OpenInv</artifactId>
<version>${openinv.version}</version>
</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.
### Compilation
Execute the gradle wrapper:
`./gradlew build`
If you encounter issues with building the Spigot module, try running BuildTools manually.
-31
View File
@@ -1,31 +0,0 @@
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
implementation(project(":openinvapi"))
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
tasks.processResources {
expand("version" to version)
}
tasks.register<Copy>("distributeAddons") {
into(rootProject.layout.projectDirectory.dir("dist"))
from(tasks.jar)
rename("openinvtogglepersist.*\\.jar", "OITogglePersist.jar")
}
tasks.assemble {
dependsOn(tasks.named("distributeAddons"))
}
@@ -1,152 +0,0 @@
package com.github.jikoo.openinv.togglepersist;
import com.google.errorprone.annotations.Keep;
import com.lishid.openinv.event.PlayerToggledEvent;
import com.lishid.openinv.util.setting.PlayerToggle;
import com.lishid.openinv.util.setting.PlayerToggles;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
public class TogglePersist extends JavaPlugin implements Listener {
private final Map<UUID, Set<String>> enabledToggles = new HashMap<>();
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
File file = new File(getDataFolder(), "toggles.yml");
// If there's no save file, there's nothing to load.
if (!file.exists()) {
return;
}
Configuration loaded = YamlConfiguration.loadConfiguration(file);
// For each toggle, enable loaded players.
for (String toggleName : loaded.getKeys(false)) {
PlayerToggle toggle = PlayerToggles.get(toggleName);
// Ensure toggle exists.
if (toggle == null) {
continue;
}
for (String idString : loaded.getStringList(toggleName)) {
// Ensure valid UUID.
UUID uuid;
try {
uuid = UUID.fromString(idString);
} catch (IllegalArgumentException e) {
continue;
}
// Track that toggle is enabled.
set(uuid, toggleName);
}
}
}
private void set(UUID playerId, String toggleName) {
enabledToggles.compute(
playerId,
(uuid, toggles) -> {
if (toggles == null) {
toggles = new HashSet<>();
}
toggles.add(toggleName);
return toggles;
}
);
}
@Override
public void onDisable() {
Map<String, List<String>> converted = getSaveData();
YamlConfiguration data = new YamlConfiguration();
for (Map.Entry<String, List<String>> playerToggle : converted.entrySet()) {
data.set(playerToggle.getKey(), playerToggle.getValue());
}
File file = new File(getDataFolder(), "toggles.yml");
try {
data.save(file);
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Unable to save player toggle states", e);
}
}
private @NotNull Map<String, List<String>> getSaveData() {
Map<String, List<String>> converted = new HashMap<>();
for (Map.Entry<UUID, Set<String>> playerToggles : enabledToggles.entrySet()) {
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;
}
);
}
}
return converted;
}
@Keep
@EventHandler
private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
UUID playerId = event.getPlayer().getUniqueId();
Set<String> toggleNames = enabledToggles.get(playerId);
if (toggleNames == null) {
return;
}
for (String toggleName : toggleNames) {
PlayerToggle toggle = PlayerToggles.get(toggleName);
if (toggle != null) {
toggle.set(playerId, true);
}
}
}
@Keep
@EventHandler
private void onToggleSet(@NotNull PlayerToggledEvent event) {
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;
}
);
}
}
}
@@ -1,7 +0,0 @@
name: OITogglePersist
main: com.github.jikoo.openinv.togglepersist.TogglePersist
version: ${version}
author: Jikoo
description: An OpenInv addon allowing /anycontainer and /silentcontainer to persist across sessions.
api-version: "1.20"
depend: [ OpenInv ]
-30
View File
@@ -1,30 +0,0 @@
plugins {
`maven-publish`
}
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
publishing {
publications {
create<MavenPublication>("jitpack") {
groupId = "com.github.Jikoo.OpenInv"
artifactId = "openinvapi"
from(components["java"])
}
}
}
@@ -1,199 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.UUID;
import java.util.logging.Logger;
/**
* Interface defining behavior for the OpenInv plugin.
*/
@NullMarked
public interface IOpenInv {
/**
* Check if the server version is supported by OpenInv.
*
* @return true if the server version is supported
*/
boolean isSupportedVersion();
/**
* Check the configuration value for whether OpenInv saves player data when unloading players. This is exclusively
* for users who do not allow editing of inventories, only viewing, and wish to prevent any possibility of bugs such
* as lishid#40. If true, OpenInv will not ever save any edits made to players.
*
* @return false unless configured otherwise
*/
boolean disableSaving();
/**
* Check the configuration value for whether OpenInv allows offline access. If true, OpenInv will not load or allow
* modification of players while they are not online. This does not prevent other plugins from using existing loaded
* players who have gone offline.
*
* @return false unless configured otherwise
* @since 4.2.0
*/
boolean disableOfflineAccess();
/**
* Check the configuration value for whether OpenInv uses history for opening commands. If false, OpenInv will use
* the previous parameterized search when no parameters are provided.
*
* @return false unless configured otherwise
* @since 4.3.0
*/
boolean noArgsOpensSelf();
/**
* Get the active {@link IAnySilentContainer} implementation.
*
* @return the active implementation for the server version
* @throws IllegalStateException if the server version is unsupported
*/
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(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(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(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(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
*/
ISpecialEnderChest getSpecialEnderChest(
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
*/
ISpecialPlayerInventory getSpecialInventory(
Player player,
boolean online
) throws InstantiationException;
/**
* @deprecated Use {@link #openInventory(Player, ISpecialInventory, boolean)}
*/
@Deprecated(forRemoval = true, since = "5.2.0")
@Nullable InventoryView openInventory(Player player, ISpecialInventory inventory);
/**
* Open an {@link ISpecialInventory} for a {@link Player}.
*
* @param player the viewer
* @param inventory the inventory to open
* @param viewOnly whether the inventory should be view-only
* @return the resulting {@link InventoryView}
*/
@Nullable InventoryView openInventory(Player player, ISpecialInventory inventory, boolean viewOnly);
/**
* Check if a {@link Player} is currently loaded by OpenInv.
*
* @param playerUuid the {@link UUID} of the {@code Player}
* @return whether the {@code Player} is loaded
* @since 4.2.0
*/
boolean isPlayerLoaded(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(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(String name);
/**
* Forcibly close inventories of and unload any cached data for a user.
*
* @param offline the {@link OfflinePlayer} to unload
*/
void unload(OfflinePlayer offline);
Logger getLogger();
}
@@ -1,57 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Event fired before OpenInv saves a player's data when closing an {@link ISpecialInventory}.
*/
@NullMarked
public class OpenPlayerSaveEvent extends PlayerSaveEvent {
private static final HandlerList HANDLERS = new HandlerList();
private final ISpecialInventory inventory;
/**
* Construct a new {@code OpenPlayerSaveEvent}.
*
* <p>The constructor is not considered part of the API, and may be subject to change.</p>
*
* @param player the player to be saved
* @param inventory the {@link ISpecialInventory} being closed
*/
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
OpenPlayerSaveEvent(Player player, ISpecialInventory inventory) {
super(player);
this.inventory = inventory;
}
/**
* Get the {@link ISpecialInventory} that triggered the save by being closed.
*
* @return the special inventory
*/
public ISpecialInventory getInventory() {
return inventory;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,68 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Event fired before a {@link Player} loaded via OpenInv is saved.
*/
@NullMarked
public class PlayerSaveEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
/**
* Construct a new {@code PlayerSaveEvent}.
*
* <p>The constructor is not considered part of the API, and may be subject to change.</p>
*
* @param player the player to be saved
*/
@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"
)
@ApiStatus.Internal
PlayerSaveEvent(Player player) {
super(player);
}
/**
* Get whether the event is cancelled.
*
* @return true if the event is cancelled
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Set whether the event is cancelled.
*
* @param cancel whether the event is cancelled
*/
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,72 +0,0 @@
package com.lishid.openinv.event;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.util.setting.PlayerToggle;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* Event fired after OpenInv modifies a toggleable setting for a player.
*/
@NullMarked
public class PlayerToggledEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final PlayerToggle toggle;
private final UUID uuid;
private final boolean enabled;
@RestrictedApi(
explanation = "Constructor is not considered part of the API and may be subject to change.",
link = "",
allowedOnPath = ".*/com/lishid/openinv/event/OpenEvents.java"
)
@ApiStatus.Internal
PlayerToggledEvent(PlayerToggle toggle, UUID uuid, boolean enabled) {
this.toggle = toggle;
this.uuid = uuid;
this.enabled = enabled;
}
/**
* Get the {@link PlayerToggle} affected.
*
* @return the toggle
*/
public PlayerToggle getToggle() {
return toggle;
}
/**
* Get the {@link UUID} of the player whose setting was changed.
*
* @return the player ID
*/
public UUID getPlayerId() {
return uuid;
}
/**
* Get whether the toggle is enabled.
*
* @return true if the toggle is enabled
*/
public boolean isEnabled() {
return enabled;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
@@ -1,133 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.EnderChest;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.util.BoundingBox;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
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(Player player, boolean silent, Block block);
/**
* Perform operations required to close the current container silently.
*
* @param player the {@link Player} closing a container
*/
void deactivateContainer(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(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(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(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(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(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(@Nullable 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 Inventory} belongs to a container which can be unblocked or silenced.
*
* @param inventory the potential container inventory
* @return true if the type is a supported container
*/
boolean isAnySilentContainer(Inventory inventory);
}
@@ -1,76 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
/**
* Interface defining behavior for special inventories backed by other inventories' content listings.
*/
@NullMarked
public interface ISpecialInventory {
/**
* Get the {@link Inventory} associated with this {@code ISpecialInventory}.
*
* @return the Bukkit inventory
*/
Inventory getBukkitInventory();
/**
* Get the {@link InventoryType} corresponding to this {@code ISpecialInventory}.
*
* @return the type of Bukkit inventory
*/
InventoryType getBukkitType();
/**
* Set the owning {@link Player} instance to a newly-joined user.
*
* @param player the user coming online
*/
void setPlayerOnline(Player player);
/**
* 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 the {@link Player} who owns the inventory.
*
* @return the {@link HumanEntity} who owns the inventory
*/
HumanEntity getPlayer();
}
@@ -1,108 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import com.google.errorprone.annotations.RestrictedApi;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.function.BiFunction;
@NullMarked
public final class InventoryAccess {
private static @Nullable BiFunction<Inventory, Class<? extends ISpecialInventory>, @Nullable ISpecialInventory> provider;
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(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(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(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(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(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>, @Nullable ISpecialInventory> provider
) {
InventoryAccess.provider = provider;
}
private InventoryAccess() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,129 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.util;
import org.bukkit.permissions.Permissible;
import org.jspecify.annotations.NullMarked;
/**
* An enum containing all permissions directly checked by OpenInv.
*
* <p>Note that this is not an exhaustive list! This does not contain
* all permissions managed by Bukkit, largely parent nodes.</p>
*/
@NullMarked
public enum Permissions {
/// Permission to open one's own inventory.
INVENTORY_OPEN_SELF("inventory.open.self"),
/// Permission to open someone else's inventory.
INVENTORY_OPEN_OTHER("inventory.open.other"),
/**
* Permission to edit one's own inventory.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #INVENTORY_OPEN_SELF} first.</p>
*/
INVENTORY_EDIT_SELF("inventory.edit.self"),
/**
* Permission to edit someone else's inventory.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #INVENTORY_OPEN_OTHER} first.</p>
*/
INVENTORY_EDIT_OTHER("inventory.edit.other"),
/// Permission to insert any item into the head slot.
INVENTORY_SLOT_HEAD_ANY("inventory.slot.head.any"),
/// Permission to insert any item into the chest slot.
INVENTORY_SLOT_CHEST_ANY("inventory.slot.chest.any"),
/// Permission to insert any item into the legs slot.
INVENTORY_SLOT_LEGS_ANY("inventory.slot.legs.any"),
/// Permission to insert any item into the feet slot.
INVENTORY_SLOT_FEET_ANY("inventory.slot.feet.any"),
/// Permission to drop items as the player via the drop slot.
INVENTORY_SLOT_DROP("inventory.slot.drop"),
/// Permission to open one's own ender chest.
ENDERCHEST_OPEN_SELF("enderchest.open.self"),
/// Permission to open someone else's ender chest.
ENDERCHEST_OPEN_OTHER("enderchest.open.other"),
/**
* Permission to edit one's own ender chest.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #ENDERCHEST_OPEN_SELF} first.</p>
*/
ENDERCHEST_EDIT_SELF("enderchest.edit.self"),
/**
* Permission to edit someone else's ender chest.
*
* <p>Note that this does not guarantee that the user has access to open the inventory!
* Be sure to check {@link #ENDERCHEST_OPEN_OTHER} first.</p>
*/
ENDERCHEST_EDIT_OTHER("enderchest.edit.other"),
/// Permission to clear one's own inventory.
CLEAR_SELF("clear.self"),
/// Permission to clear someone else's inventory.
CLEAR_OTHER("clear.other"),
/// Permission to view inventories and ender chests from other worlds.
ACCESS_CROSSWORLD("access.crossworld"),
/// Permission to access offline players' inventories and ender chests.
ACCESS_OFFLINE("access.offline"),
/// Permission to access online players' inventories and ender chests.
ACCESS_ONLINE("access.online"),
/// Permission granting the ability to edit players with the same access level.
ACCESS_EQUAL_EDIT("access.equal.edit"),
/// Permission to view, but not edit, players with the same access level.
ACCESS_EQUAL_VIEW("access.equal.view"),
/// Permission to deny access to players with the same access level.
ACCESS_EQUAL_DENY("access.equal.deny"),
/// Permission to perform inventory interaction while in spectator mode.
SPECTATE_CLICK("spectate.click"),
/// Permission to use the AnyContainer feature.
CONTAINER_ANY("container.any"),
/// Permission to use the AnyContainer feature but not the command to toggle it.
CONTAINER_ANY_USE("container.any.use"),
/// Permission to use the SilentContainer feature.
CONTAINER_SILENT("container.silent"),
/// Permission to use the SilentContainer feature but not the command to toggle it.
CONTAINER_SILENT_USE("container.silent.use"),
/// Permission to search inventories and ender chests.
SEARCH_INVENTORY("search.inventory"),
/// Permission to search containers.
SEARCH_CONTAINER("search.container");
private final String permission;
Permissions(String permission) {
this.permission = "openinv." + permission;
}
/**
* Check if a {@link Permissible} has this permission.
*
* @param permissible the Permissible
* @return true if the permission is granted
*/
public boolean hasPermission(Permissible permissible) {
return permissible.hasPermission(permission);
}
}
@@ -1,47 +0,0 @@
package com.lishid.openinv.util.setting;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* A per-player setting that may be enabled or disabled.
*/
@NullMarked
public interface PlayerToggle {
/**
* Get the name of the setting.
*
* @return the setting name
*/
String getName();
/**
* Get the state of the toggle for a particular player ID.
*
* @param uuid the player ID
* @return true if the setting is enabled
*/
boolean is(UUID uuid);
/**
* Get the state of the toggle for a particular {@link Player},
* accounting for permissions required to use the feature.
*
* @param player the player
* @return true if the setting is enabled and the player has the required permissions
*/
boolean is(Player player);
/**
* Set the state of the toggle for a particular player ID.
*
* @param uuid the player ID
* @param enabled whether the setting is enabled
* @return true if the setting changed as a result of being set
*/
boolean set(UUID uuid, boolean enabled);
}
@@ -1,121 +0,0 @@
package com.lishid.openinv.util.setting;
import com.lishid.openinv.util.Permissions;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.UnmodifiableView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Utility class containing all of OpenInv's {@link PlayerToggle PlayerToggles}.
*/
@NullMarked
public final class PlayerToggles {
private static final Map<String, PlayerToggle> TOGGLES = new HashMap<>();
private static final PlayerToggle ANY = add(new MemoryToggle("AnyContainer") {
@Override
public boolean is(Player player) {
return is(player.getUniqueId())
&& (Permissions.CONTAINER_ANY.hasPermission(player) || Permissions.CONTAINER_ANY_USE.hasPermission(player));
}
});
private static final PlayerToggle SILENT = add(new MemoryToggle("SilentContainer") {
@Override
public boolean is(Player player) {
return is(player.getUniqueId())
&& (Permissions.CONTAINER_SILENT.hasPermission(player) || Permissions.CONTAINER_SILENT_USE.hasPermission(player));
}
});
/**
* Get the AnyContainer toggle.
*
* @return the AnyContainer toggle
*/
public static PlayerToggle any() {
return ANY;
}
/**
* Get the SilentContainer toggle.
*
* @return the SilentContainer toggle
*/
public static PlayerToggle silent() {
return SILENT;
}
/**
* Get a toggle by name.
*
* @param toggleName the name of the toggle
* @return the toggle, or null if no such toggle exists.
*/
public static @Nullable PlayerToggle get(String toggleName) {
PlayerToggle toggle = TOGGLES.get(toggleName);
if (toggle == null) {
toggle = TOGGLES.get(toggleName.toLowerCase(Locale.ENGLISH));
}
return toggle;
}
/**
* Get an unmodifable view of all toggles available.
*
* @return a view of all toggles available
*/
public static @UnmodifiableView Collection<PlayerToggle> get() {
return Collections.unmodifiableCollection(TOGGLES.values());
}
private static PlayerToggle add(PlayerToggle toggle) {
TOGGLES.put(toggle.getName().toLowerCase(Locale.ENGLISH), toggle);
return toggle;
}
private PlayerToggles() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
private static abstract class MemoryToggle implements PlayerToggle {
private final Set<UUID> enabled;
private final String name;
private MemoryToggle(String name) {
enabled = new HashSet<>();
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public boolean is(UUID uuid) {
return enabled.contains(uuid);
}
@Override
public boolean set(UUID uuid, boolean enabled) {
if (enabled) {
return this.enabled.add(uuid);
} else {
return this.enabled.remove(uuid);
}
}
}
}
-47
View File
@@ -1,47 +0,0 @@
plugins {
`java-library`
alias(libs.plugins.paperweight) apply false
alias(libs.plugins.shadow) apply false
alias(libs.plugins.errorprone.gradle)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Allow submodules to target higher Java release versions.
java.disableAutoTargetJvm()
subprojects {
apply(plugin = "java-library")
apply(plugin = rootProject.libs.plugins.errorprone.gradle.get().pluginId)
repositories {
mavenCentral()
}
dependencies {
compileOnly(rootProject.libs.jspecify)
compileOnly(rootProject.libs.annotations)
errorprone(rootProject.libs.errorprone.core)
}
tasks {
withType<JavaCompile>().configureEach {
options.encoding = Charsets.UTF_8.name()
}
withType<Javadoc>().configureEach {
options.encoding = Charsets.UTF_8.name()
}
}
}
// Task to delete ./dist where final files are output.
tasks.register("cleanDist") {
delete("dist")
}
tasks.clean {
// Also delete distribution folder when cleaning.
dependsOn(tasks.named("cleanDist"))
}
-8
View File
@@ -1,8 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
View File
Whitespace-only changes.
@@ -1,107 +0,0 @@
package com.github.jikoo.openinv
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
import java.io.File
import java.net.URI
import java.nio.file.Files
import javax.inject.Inject
abstract class BuildToolsValueSource : ValueSource<File, BuildToolsValueSource.Parameters> {
@get:Inject
abstract val exec: ExecOperations
interface Parameters : ValueSourceParameters {
val installDir: DirectoryProperty
val workingDir: DirectoryProperty
val spigotVersion: Property<String>
val spigotRevision: Property<String>
val ignoreCached: Property<Boolean>
val javaHome: DirectoryProperty
val javaExecutable: Property<String>
}
override fun obtain(): File {
val version = parameters.spigotVersion.get()
val revision = parameters.spigotRevision.get()
val spigotLocation = parameters.installDir.get().asFile.resolve("spigot-$version.jar")
// If Spigot is already installed, don't reinstall.
if (!parameters.ignoreCached.get() && spigotLocation.exists()) {
println("Skipping Spigot installation, $version is present")
return spigotLocation
}
val buildTools = installBuildTools(parameters.workingDir.get().asFile)
println("Installing Spigot $version (rev $revision)")
exec.javaexec {
environment["JAVA_HOME"] = parameters.javaHome.get()
executable = parameters.javaExecutable.get()
workingDir = buildTools.parentFile
classpath(buildTools)
args = listOf("--nogui", "--rev", revision)
}.rethrowFailure()
val spigotBuild = buildTools.parentFile.resolve("Spigot/Spigot-Server/target/spigot-$version.jar")
if (!spigotBuild.exists()) {
throw IllegalStateException(
"Spigot build completed, but $spigotBuild does not exist! Did BuildTools change output location?"
)
}
spigotBuild.copyTo(spigotLocation, overwrite = true)
// Mark work for delete.
cleanUp(buildTools.parentFile)
if (!spigotLocation.exists()) {
throw IllegalStateException(
"Failed to install Spigot $version from $revision. Does the revision point to a different version?"
)
}
return spigotLocation
}
private fun installBuildTools(workingDir: File): File {
val buildTools = workingDir.resolve("BuildTools.jar")
if (buildTools.exists()) {
return buildTools
}
workingDir.mkdirs()
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())
stream.close()
return buildTools
}
private fun cleanUp(dir: File) {
dir.deleteOnExit()
if (!dir.isDirectory) {
return
}
dir.listFiles()?.forEach {
if (it.isDirectory) {
cleanUp(it)
} else {
it.deleteOnExit()
}
}
}
}
@@ -1,19 +0,0 @@
package com.github.jikoo.openinv
import org.gradle.api.model.ObjectFactory
import org.gradle.jvm.toolchain.JavaToolchainSpec
abstract class SpigotDependencyExtension(
objects: ObjectFactory
) {
val version = objects.property(String::class.java)
val revision = objects.property(String::class.java)
.convention(version.map {
it.replace("-R\\d+\\.\\d+-SNAPSHOT".toRegex(), "")
})
val classifier = objects.property(String::class.java).convention("")
val java = objects.property(JavaToolchainSpec::class.java)
val ignoreCached = objects.property(Boolean::class.java).convention(false)
}
@@ -1,50 +0,0 @@
package com.github.jikoo.openinv
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaToolchainService
import java.io.File
import javax.inject.Inject
abstract class SpigotSetup : Plugin<Project> {
@get:Inject
abstract val javaToolchainService: JavaToolchainService
override fun apply(target: Project) {
target.plugins.apply("java")
// Set up extension for configuring Spigot dependency.
val spigotExt = target.dependencies.extensions.findByType(SpigotDependencyExtension::class.java)
?: target.dependencies.extensions.create(
"spigot",
SpigotDependencyExtension::class.java,
target.objects
)
target.afterEvaluate {
// Get Java requirements, defaulting to version used for compilation.
spigotExt.java.convention(target.extensions.getByType(JavaPluginExtension::class.java).toolchain)
val launcher = javaToolchainService.launcherFor(spigotExt.java.get()).get()
// Install Spigot with BuildTools.
val spigot: File = target.providers.of(BuildToolsValueSource::class.java) {
parameters {
installDir.set(target.gradle.gradleUserHomeDir.resolve("caches/spigot"))
workingDir.set(target.layout.buildDirectory.dir("tmp/buildtools"))
spigotVersion.set(spigotExt.version)
spigotRevision.set(spigotExt.revision)
ignoreCached.set(spigotExt.ignoreCached)
javaHome.set(launcher.metadata.installationPath)
javaExecutable.set(launcher.executablePath.asFile.path)
}
}.get()
// Add Spigot dependency.
val dependency = target.dependencies.create(files(spigot))
target.dependencies.add("compileOnly", dependency)
}
}
}
-20
View File
@@ -1,20 +0,0 @@
repositories {
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
}
dependencies {
compileOnly(libs.spigotapi)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
dependencies {
implementation(project(":openinvapi"))
compileOnly(libs.slf4j.api)
}
@@ -1,28 +0,0 @@
package com.github.jikoo.openinv.internal;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public interface Accessor {
PlayerManager getPlayerManager();
IAnySilentContainer getAnySilentContainer();
ISpecialPlayerInventory createPlayerInventory(Player player);
ISpecialEnderChest createEnderChest(Player player);
<T extends ISpecialInventory> @Nullable T get(Inventory bukkitInventory, Class<T> clazz);
void reload(ConfigurationSection config);
}
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2011-2022 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.jikoo.openinv.internal;
import com.lishid.openinv.internal.ISpecialInventory;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
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(OfflinePlayer offline);
/**
* Creates a new Player from an existing one that will function slightly better offline.
*
* @return the Player
*/
Player inject(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(Player player, ISpecialInventory inventory, boolean viewOnly);
}
@@ -1,107 +0,0 @@
package com.github.jikoo.openinv.internal.container;
import com.lishid.openinv.internal.IAnySilentContainer;
import org.bukkit.block.Barrel;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.EnderChest;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Chest;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public abstract class AnySilentContainerBase implements IAnySilentContainer {
public enum StateType {
ENDER_CHEST,
SHULKER_BOX,
CHEST,
BARREL,
OTHER
}
@Override
public boolean isAnyContainerNeeded(Block block) {
return isAnyContainerNeeded(getStateType(block), block);
}
protected boolean isAnyContainerNeeded(StateType material, Block block) {
return switch (material) {
// Enderchests require a non-occluding block on top to open.
case ENDER_CHEST -> block.getRelative(0, 1, 0).getType().isOccluding();
// Shulker boxes require half a block clear in the direction they open.
case SHULKER_BOX -> isShulkerBlocked(block);
// Chests require that the block above not be occluding or occupied by a cat.
case CHEST -> isChestBlocked(block);
// Barrels need silent but not any. All others unsupported.
default -> false;
};
}
@Override
public boolean isChestBlocked(Block block) {
if (IAnySilentContainer.super.isChestBlocked(block)) {
return true;
}
BlockData blockData = block.getBlockData();
if (!(blockData instanceof Chest chest) || chest.getType() == Chest.Type.SINGLE) {
return false;
}
BlockFace relativeFace = switch (chest.getFacing()) {
case NORTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.WEST : BlockFace.EAST;
case EAST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.NORTH : BlockFace.SOUTH;
case SOUTH -> chest.getType() == Chest.Type.RIGHT ? BlockFace.EAST : BlockFace.WEST;
case WEST -> chest.getType() == Chest.Type.RIGHT ? BlockFace.SOUTH : BlockFace.NORTH;
default -> BlockFace.SELF;
};
Block relative = block.getRelative(relativeFace);
if (relative.getType() != block.getType()) {
return false;
}
BlockData relativeData = relative.getBlockData();
if (!(relativeData instanceof Chest relativeChest)) {
return false;
}
if (relativeChest.getFacing() != chest.getFacing()
|| relativeChest.getType() != (chest.getType() == Chest.Type.RIGHT ? Chest.Type.LEFT : Chest.Type.RIGHT)) {
return false;
}
return IAnySilentContainer.super.isChestBlocked(relative);
}
@Override
public boolean isAnySilentContainer(Block block) {
return isAnySilentContainer(getState(block));
}
@Override
public boolean isAnySilentContainer(Inventory inventory) {
return isAnySilentContainer(getHolder(inventory));
}
protected StateType getStateType(Block block) {
return switch (getState(block)) {
case Barrel ignored -> StateType.BARREL;
case EnderChest ignored -> StateType.ENDER_CHEST;
case ShulkerBox ignored -> StateType.SHULKER_BOX;
case org.bukkit.block.Chest ignored -> StateType.CHEST;
default -> StateType.OTHER;
};
}
protected abstract BlockState getState(Block block);
protected abstract @Nullable InventoryHolder getHolder(Inventory inventory);
}
@@ -1,10 +0,0 @@
package com.github.jikoo.openinv.internal.container;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface InternalOwned<T> {
T getOwnerHandle();
}
@@ -1,4 +0,0 @@
package com.github.jikoo.openinv.internal.container;
public interface ViewOnly {
}
@@ -1,66 +0,0 @@
package com.github.jikoo.openinv.internal.container.slot;
import org.jspecify.annotations.NullMarked;
/**
* An interface defining behaviors for entries in a {@link net.minecraft.world.Container Container}. Used to reduce duplicate content reordering.
*/
@NullMarked
public interface Content<P, I, C, S> {
/**
* Update internal holder.
*
* @param holder the new holder
*/
void setHolder(P holder);
/**
* Get the current item.
*
* @return the current item
*/
I get();
/**
* Remove the current item.
*
* @return the current item
*/
I remove();
/**
* Remove some of the current item.
*
* @return the current item
*/
I removePartial(int amount);
/**
* Set the current item. If slot is currently not usable, will drop item instead.
*
* @param itemStack the item to set
*/
void set(I itemStack);
/**
* Get a {@link net.minecraft.world.inventory.Slot Slot} for use in a {@link net.minecraft.world.inventory.AbstractContainerMenu ContainerMenu}. Will
* impose any specific restrictions to insertion or removal.
*
* @param container the backing container
* @param slot the slot of the backing container represented
* @param x clientside x dimension from top left of inventory, not used
* @param y clientside y dimension from top left of inventory, not used
* @return a menu slot
*/
S asSlot(C container, int slot, int x, int y);
/**
* Get a loose Bukkit translation of what this slot stores. For example, any slot that drops items at the owner rather
* than insert them will report itself as being {@link org.bukkit.event.inventory.InventoryType.SlotType#OUTSIDE}.
*
* @return the closes Bukkit slot type
*/
org.bukkit.event.inventory.InventoryType.SlotType getSlotType();
}
@@ -1,35 +0,0 @@
package com.github.jikoo.openinv.internal.container.slot;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.function.Function;
@NullMarked
public interface InventoryFactory<P, I, C, S, E> {
Content<P, I, C, S> newCrafting(P holder, int index);
Content<P, I, C, S> newCraftingResult(P holder);
Content<P, I, C, S> newCursor(P holder);
Content<P, I, C, S> newDrop(P holder);
Content<P, I, C, S> newEquipment(P holder, E equipmentSlot);
Content<P, I, C, S> newList(P holder, int index, InventoryType.SlotType slotType, Function<P, List<I>> listSupplier);
Content<P, I, C, S> newOffHand(P holder);
Content<P, I, C, S> newViewOnly(P holder);
Content<P, I, C, S> newViewOnly(P holder, I itemStack);
S wrapViewOnly(S wrapped);
ItemStack asBukkitMirror(I itemStack);
}
@@ -1,309 +0,0 @@
package com.github.jikoo.openinv.lang;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.logging.Level;
/**
* A simple language manager supporting both custom and bundled languages.
*/
@NullMarked
public class LanguageManager {
private final Plugin plugin;
private final File folder;
private final String defaultLocale;
private final Map<String, YamlConfiguration> locales;
public LanguageManager(Plugin plugin, 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();
}
public void reload() {
this.locales.clear();
getOrLoadLocale(defaultLocale);
}
private YamlConfiguration getOrLoadLocale(String locale) {
YamlConfiguration loaded = locales.get(locale);
if (loaded != null) {
return loaded;
}
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;
}
private LangLocation bestMatch(String locale, @Nullable LangLocation initial) {
File file = new File(folder, locale + ".yml");
InputStream bundled = plugin.getResource("locale/" + locale + ".yml");
if (file.exists() || bundled != null) {
return new LangLocation(locale, file, bundled);
}
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);
}
private YamlConfiguration loadLocale(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();
}
}
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;
}
// 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;
}
// 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;
}
private void addTranslationFallthrough(LangLocation location, 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);
}
private List<String> getMissingKeys(
Configuration configurationDefault,
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;
}
public @Nullable String getValue(String key, @Nullable String locale) {
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase(Locale.ENGLISH)).getString(key);
if (value == null || value.isBlank()) {
return null;
}
value = ChatColor.translateAlternateColorCodes('&', value);
return value;
}
public @Nullable String getValue(
String key,
@Nullable String locale,
Replacement... 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(CommandSender sender, String key) {
return getValue(key, getLocale(sender));
}
public @Nullable String getLocalizedMessage(
CommandSender sender,
String key,
Replacement... replacements
) {
return getValue(key, getLocale(sender), replacements);
}
private String getLocale(CommandSender sender) {
if (sender instanceof Player player) {
return player.getLocale();
} else {
return plugin.getConfig().getString("settings.locale", "en");
}
}
public void sendMessage(CommandSender sender, String key) {
String message = getLocalizedMessage(sender, key);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendMessage(CommandSender sender, String key, Replacement... replacements) {
String message = getLocalizedMessage(sender, key, replacements);
if (message != null && !message.isEmpty()) {
sender.sendMessage(message);
}
}
public void sendSystemMessage(Player player, 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(String locale, File file, @Nullable InputStream bundled) {}
}
@@ -1,14 +0,0 @@
package com.github.jikoo.openinv.lang;
import org.jspecify.annotations.NullMarked;
/**
* A data holder for string replacement in translations.
*
* @param placeholder the placeholder to be replaced
* @param value the value to insert
*/
@NullMarked
public record Replacement(String placeholder, String value) {
}
@@ -1,131 +0,0 @@
package com.github.jikoo.openinv.util;
import org.jspecify.annotations.NullMarked;
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.
*/
@NullMarked
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) {
StackWalker.getInstance().walk(stream -> stream
// Start at index 2 - this is an internal method, so we want at least the caller's caller.
.skip(2)
// Walk at maximum 10 frames.
.limit(10)
.filter(frame -> isIgnored(frame.getClassName(), fqcn))
.findFirst()
).ifPresent(frame -> {
logRecord.setSourceClassName(frame.getClassName());
logRecord.setSourceMethodName(frame.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);
}
}
@@ -1,64 +0,0 @@
package com.github.jikoo.openinv.util;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
/**
* A utility for making reflection easier.
*/
@NullMarked
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);
if (field != null) {
try {
return fieldType.cast(field.get(holder));
} catch (IllegalAccessException ignored) {
// Ignore issues obtaining field
}
}
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;
}
}
if (holderType.getSuperclass() != null) {
return grabFieldByType(fieldType, holderType.getSuperclass());
}
return null;
}
private ReflectionHelper() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,40 +0,0 @@
package com.lishid.openinv.event;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.util.setting.PlayerToggle;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
/**
* Construct and call events.
*/
@NullMarked
public final class OpenEvents {
public static boolean saveCancelled(Player player) {
return call(new PlayerSaveEvent(player));
}
public static boolean saveCancelled(ISpecialInventory inventory) {
return call(new OpenPlayerSaveEvent((Player) inventory.getPlayer(), inventory));
}
public static void notifyPlayerToggle(PlayerToggle toggle, UUID uuid, boolean state) {
Bukkit.getPluginManager().callEvent(new PlayerToggledEvent(toggle, uuid, state));
}
private static <T extends Event & Cancellable> boolean call(T event) {
Bukkit.getPluginManager().callEvent(event);
return event.isCancelled();
}
private OpenEvents() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
View File
File renamed without changes.
-9
View File
@@ -1,9 +0,0 @@
# Project meta
group=com.lishid.openinv
version=5.3.4
description=A Bukkit plugin for opening normally-inaccessible inventories.
# Gradle configuration
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
-33
View File
@@ -1,33 +0,0 @@
[versions]
spigotapi = "1.21.3-R0.1-SNAPSHOT"
planarwrappers = "3.3.0"
jspecify = "1.0.1"
annotations = "26.1.0"
paperweight = "2.0.0-beta.21"
shadow = "9.6.1"
folia-scheduler-wrapper = "v0.0.3"
errorprone-core = "2.50.0"
errorprone-gradle = "5.1.1"
slf4j = "2.0.18"
sqlite-jdbc = "3.49.1.0"
mockito-core = "5.23.0"
junit-jupiter = "6.1.3"
hamcrest = "3.0"
[libraries]
spigotapi = { module = "org.spigotmc:spigot-api", version.ref = "spigotapi" }
planarwrappers = { module = "com.github.jikoo:planarwrappers", version.ref = "planarwrappers" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
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" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
[plugins]
paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
errorprone-gradle = { id = "net.ltgt.errorprone", version.ref = "errorprone-gradle" }
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
-248
View File
@@ -1,248 +0,0 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# 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
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
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
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# 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, 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# 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.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
-82
View File
@@ -1,82 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@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
@rem ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
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
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
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
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
-25
View File
@@ -1,25 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvadapterpaper26_1")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT")
}
@@ -1,30 +0,0 @@
package com.lishid.openinv.internal.paper1_21_11;
import com.github.jikoo.openinv.lang.LanguageManager;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.lishid.openinv.internal.paper1_21_11.container.OpenEnderChest;
import com.lishid.openinv.internal.paper1_21_11.container.OpenInventory;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_1.InternalAccessor {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
@Override
public ISpecialPlayerInventory createPlayerInventory(Player player) {
return new OpenInventory(factory, player);
}
}
@@ -1,35 +0,0 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_3.container.OpenEnderChest {
public OpenEnderChest(org.bukkit.entity.Player player) {
super(player);
}
@Override
public @Nullable OpenChestMenu<?> createMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player player,
int i,
boolean viewOnly
) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,33 +0,0 @@
package com.lishid.openinv.internal.paper1_21_11.container;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper1_21_11.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventory extends com.lishid.openinv.internal.paper26_3.container.OpenInventory {
public OpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
org.bukkit.entity.Player bukkitPlayer
) {
super(factory, bukkitPlayer);
}
@Override
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,72 +0,0 @@
package com.lishid.openinv.internal.paper1_21_11.container.menu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenSyncMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, factory, enderChest, viewer, viewOnly);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu
Slot slot = this.slots.get(index);
if (slot.isFake() || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack original = itemStack.copy();
if (index < topSize) {
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
return ItemStack.EMPTY;
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return original;
}
@Override
public void clicked(int i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
}
@@ -1,39 +0,0 @@
package com.lishid.openinv.internal.paper1_21_11.container.menu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.menu.BaseOpenInventoryMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryMenu extends BaseOpenInventoryMenu {
public OpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(factory, inventory, viewer, i, viewOnly);
}
@Override
public void clicked(int i, int j, ClickType clickType, Player player) {
if (viewOnly) {
if (clickType == ClickType.QUICK_CRAFT) {
sendAllDataToRemote();
}
return;
}
super.clicked(i, j, clickType, player);
}
}
-45
View File
@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
api(project(":openinvadapterpaper26_2")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("26.1.2.build.+")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -1,53 +0,0 @@
package com.lishid.openinv.internal.paper26_1;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_1.container.AnySilentContainer;
import com.lishid.openinv.internal.paper26_1.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_1.player.PlayerManager;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
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.entity.Player;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_2.InternalAccessor {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
protected PlayerManager createPlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
return new PlayerManager(logger, factory);
}
@Override
protected AnySilentContainer createAnySilentContainer(
Logger logger,
LanguageManager lang
) {
return new AnySilentContainer(logger, lang);
}
@Override
protected PlaceholderLoader createPlaceholderLoader() {
return new com.lishid.openinv.internal.paper26_1.container.slot.placeholder.PlaceholderLoader();
}
@Override
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
}
@@ -1,21 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class AnySilentContainer extends com.lishid.openinv.internal.paper26_3.container.AnySilentContainer {
public AnySilentContainer(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
protected boolean hasLootTable(RandomizableContainerBlockEntity lootable) {
return lootable.lootTable != null;
}
}
@@ -1,20 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container;
import net.minecraft.core.NonNullList;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChest extends com.lishid.openinv.internal.paper26_3.container.OpenEnderChest {
public OpenEnderChest(org.bukkit.entity.Player player) {
super(player);
}
@Override
protected NonNullList<ItemStack> getEnderChestItems(ServerPlayer owner) {
return owner.getEnderChestInventory().items;
}
}
@@ -1,20 +0,0 @@
package com.lishid.openinv.internal.paper26_1.container.slot.placeholder;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PlaceholderLoader extends com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader {
@Override
protected Item getDefaultCursorItem() {
return Items.WHITE_BANNER;
}
@Override
protected Item getDefaultNotSlotItem() {
return Items.WHITE_STAINED_GLASS_PANE;
}
}
@@ -1,50 +0,0 @@
package com.lishid.openinv.internal.paper26_1.player;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class PlayerManager extends com.lishid.openinv.internal.paper26_3.player.PlayerManager {
public PlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
super(logger, factory);
}
@Override
protected void removeAdvancementListeners(ServerPlayer entity) {
entity.getAdvancements().stopListening();
}
@Override
protected LevelData.RespawnData getRespawnData(ServerLevel level) {
return level.levelData.getRespawnData();
}
@Override
protected Vec3 getAdjustedSpawnLocation(
ServerPlayer player,
ServerLevel level,
LevelData.RespawnData respawnData
) {
return player.adjustSpawnLocation(level, respawnData.pos()).getBottomCenter();
}
@Override
protected void setServerLevel(ServerPlayer player, ServerLevel level) {
player.spawnIn(level);
}
}
-45
View File
@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
api(project(":openinvadapterpaper26_3")) {
exclude(group = "io.papermc.paper", module = "dev-bundle")
}
paperweight.paperDevBundle("26.2.build.+")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -1,27 +0,0 @@
package com.lishid.openinv.internal.paper26_2;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_2.container.slot.OpenInventoryFactory;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
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.jspecify.annotations.NullMarked;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.lishid.openinv.internal.paper26_3.InternalAccessor {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
protected InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> createInventoryFactory() {
return new OpenInventoryFactory();
}
}
@@ -1,95 +0,0 @@
package com.lishid.openinv.internal.paper26_2.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentCrafting;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentCursor;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentViewOnly;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryFactory extends com.lishid.openinv.internal.paper26_3.container.slot.OpenInventoryFactory {
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCrafting(
ServerPlayer holder,
int index
) {
return new ContentCrafting(holder, index) {
@Override
public void set(ItemStack itemStack) {
if (isAvailable(holder)) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
this.holder.drop(itemStack, false);
}
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCursor(ServerPlayer holder) {
return new ContentCursor(holder) {
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
this.holder.drop(itemStack, false);
}
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newDrop(ServerPlayer holder) {
return new ContentDrop(holder) {
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder) {
return newViewOnly(holder, Placeholders.notSlot);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(
ServerPlayer holder,
ItemStack itemStack
) {
return new ContentViewOnly(holder) {
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return itemStack;
}
};
}
};
}
@Override
public org.bukkit.inventory.ItemStack asBukkitMirror(ItemStack itemStack) {
return CraftItemStack.asCraftMirror(itemStack);
}
}
-42
View File
@@ -1,42 +0,0 @@
plugins {
alias(libs.plugins.paperweight)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
// Paper is compiled with Java 25, so we have to use a Java 25 toolchain.
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
// To minimize duplicate code, the adapter for 1.21.11 is built off of this module, so we need to target Java 21.
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
// As a result, we have to specifically tell Gradle it's okay if our dependencies require Java 25,
// because secretly sometimes they don't!
configurations.matching { it.isCanBeResolved && it.name.endsWith("Classpath") }.all {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
dependencies {
implementation(project(":openinvapi")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
implementation(project(":openinvcommon")) {
exclude(group = "org.spigotmc", module = "spigot-api")
}
paperweight.paperDevBundle("26.3.build.+")
}
tasks.reobfJar {
enabled = false
}
configurations.reobf.get().outgoing.artifacts.clear()
@@ -1,111 +0,0 @@
package com.lishid.openinv.internal.paper26_3;
import com.github.jikoo.openinv.internal.Accessor;
import com.lishid.openinv.internal.IAnySilentContainer;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.lishid.openinv.internal.ISpecialInventory;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.AnySilentContainer;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.OpenInventory;
import com.lishid.openinv.internal.paper26_3.container.slot.OpenInventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.PlaceholderLoader;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
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.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.logging.Level;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor implements Accessor {
protected final Logger logger;
private final PlayerManager manager;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
private final AnySilentContainer anySilentContainer;
public InternalAccessor(Logger logger, LanguageManager lang) {
this.logger = logger;
factory = createInventoryFactory();
manager = createPlayerManager(logger, factory);
anySilentContainer = createAnySilentContainer(logger, lang);
}
protected InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> createInventoryFactory() {
return new OpenInventoryFactory();
}
protected PlayerManager createPlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
return new PlayerManager(logger, factory);
}
protected AnySilentContainer createAnySilentContainer(
Logger logger,
LanguageManager lang
) {
return new AnySilentContainer(logger, lang);
}
protected PlaceholderLoader createPlaceholderLoader() {
return new PlaceholderLoader();
}
@Override
public PlayerManager getPlayerManager() {
return manager;
}
@Override
public IAnySilentContainer getAnySilentContainer() {
return anySilentContainer;
}
@Override
public ISpecialPlayerInventory createPlayerInventory(Player player) {
return new OpenInventory(factory, player);
}
@Override
public ISpecialEnderChest createEnderChest(Player player) {
return new OpenEnderChest(player);
}
@Override
public <T extends ISpecialInventory> @Nullable T get(Inventory bukkitInventory, Class<T> clazz) {
if (!(bukkitInventory instanceof CraftInventory craftInventory)) {
return null;
}
Container container = craftInventory.getInventory();
if (clazz.isInstance(container)) {
return clazz.cast(container);
}
return null;
}
@Override
public void reload(ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
try {
// Reset placeholders to defaults and try to load configuration.
createPlaceholderLoader().load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
@@ -1,233 +0,0 @@
/*
* Copyright (C) 2011-2023 lishid. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lishid.openinv.internal.paper26_3.container;
import com.github.jikoo.openinv.internal.container.AnySilentContainerBase;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import com.github.jikoo.openinv.util.ReflectionHelper;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
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;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.TrappedChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.logging.Logger;
@NullMarked
public class AnySilentContainer extends AnySilentContainerBase {
private final Logger logger;
private final LanguageManager lang;
private @Nullable Field serverPlayerGameModeGameType;
public AnySilentContainer(Logger logger, 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(
final Player bukkitPlayer,
final boolean silentchest,
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;
}
ServerPlayer player = PlayerManager.getHandle(bukkitPlayer);
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);
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) -> {
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 @Nullable 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 (hasLootTable(lootable)) {
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;
}
protected boolean hasLootTable(RandomizableContainerBlockEntity lootable) {
return lootable.getLootTable() != null;
}
@Override
public void deactivateContainer(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);
}
}
@Override
protected org.bukkit.block.BlockState getState(org.bukkit.block.Block block) {
return block.getState(false);
}
@Override
protected @Nullable InventoryHolder getHolder(Inventory inventory) {
return inventory.getHolder(false);
}
}
@@ -1,313 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container;
import com.lishid.openinv.internal.ISpecialPlayerInventory;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenPlayerInventory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenInventoryMenu;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@NullMarked
public abstract class BaseOpenInventory implements Container, InternalOwned<ServerPlayer>, ISpecialPlayerInventory {
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected final List<Content<ServerPlayer, ItemStack, Container, Slot>> slots;
private final int size;
protected ServerPlayer owner;
private int maxStackSize = 99;
protected @Nullable CraftInventory bukkitEntity;
public List<HumanEntity> transaction = new ArrayList<>();
public BaseOpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
org.bukkit.entity.Player bukkitPlayer
) {
this.factory = factory;
owner = PlayerManager.getHandle(bukkitPlayer);
// Get total size, rounding up to nearest 9 for client compatibility.
int rawSize = owner.getInventory().getContainerSize() + owner.inventoryMenu.getCraftSlots().getContainerSize() + 1;
size = ((int) Math.ceil(rawSize / 9.0)) * 9;
slots = NonNullList.withSize(size, factory.newViewOnly(owner));
setupSlots();
}
protected void setupSlots() {
// Top of inventory: Regular contents.
int nextIndex = addMainInventory();
// If inventory is expected size, we can arrange slots to be pretty.
Inventory ownerInv = owner.getInventory();
if (ownerInv.getNonEquipmentItems().size() == 36
&& 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.
addOffHand(46);
// Drop slot: Bottom right.
slots.set(53, factory.newDrop(owner));
// Cursor slot: Above drop.
slots.set(44, factory.newCursor(owner));
// Crafting is displayed in the bottom right corner.
// As we're using the pretty view, this is a 3x2.
addCrafting(41, true);
return;
}
// Otherwise we'll just add elements linearly.
nextIndex = addArmor(nextIndex);
nextIndex = addOffHand(nextIndex);
nextIndex = addCrafting(nextIndex, false);
slots.set(nextIndex, factory.newCursor(owner));
// Drop slot last.
slots.set(slots.size() - 1, factory.newDrop(owner));
}
private int addMainInventory() {
int listSize = owner.getInventory().getNonEquipmentItems().size();
// Hotbar slots are 0-8. We want those to appear on the bottom of the inventory like a normal player inventory,
// so everything else needs to move up a row.
int hotbarDiff = listSize - 9;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
InventoryType.SlotType type;
int invIndex;
if (localIndex < hotbarDiff) {
invIndex = localIndex + 9;
type = InventoryType.SlotType.CONTAINER;
} else {
type = InventoryType.SlotType.QUICKBAR;
invIndex = localIndex - hotbarDiff;
}
slots.set(
localIndex,
factory.newList(owner, invIndex, type, holder -> holder.getInventory().getNonEquipmentItems())
);
}
return listSize;
}
private int addArmor(int startIndex) {
// Armor slots go bottom to top; boots are first and helmet is last.
// Since we have to display horizontally due to space restrictions,
// making the left side the "top" is more user-friendly.
EquipmentSlot[] sorted = Inventory.EQUIPMENT_SLOT_MAPPING.int2ObjectEntrySet()
.stream()
.sorted(Comparator.comparingInt(Int2ObjectMap.Entry::getIntKey))
.map(Map.Entry::getValue)
.toArray(EquipmentSlot[]::new);
int localIndex = 0;
for (int i = sorted.length - 1; i >= 0; --i) {
// Skip off-hand, handled separately. Also skip non-player slots.
if (sorted[i].getType() != EquipmentSlot.Type.HUMANOID_ARMOR) {
continue;
}
slots.set(startIndex + localIndex, factory.newEquipment(owner, sorted[i]));
++localIndex;
}
return startIndex + localIndex;
}
private int addOffHand(int startIndex) {
// No off-hand?
if (!Inventory.EQUIPMENT_SLOT_MAPPING.containsValue(EquipmentSlot.OFFHAND)) {
return startIndex;
}
slots.set(startIndex, factory.newOffHand(owner));
return startIndex + 1;
}
private int addCrafting(int startIndex, boolean pretty) {
int listSize = owner.inventoryMenu.getCraftSlots().getContents().size();
pretty &= listSize == 4;
for (int localIndex = 0; localIndex < listSize; ++localIndex) {
// Pretty display is a 2x2 rather than linear.
// If index is in top row, grid is not 2x2, or pretty is disabled, just use current index.
// Otherwise, subtract 2 and add 9 to start in the same position on the next row.
int modIndex = startIndex + (localIndex < 2 || !pretty ? localIndex : localIndex + 7);
slots.set(modIndex, factory.newCrafting(owner, localIndex));
}
if (pretty) {
slots.set(startIndex + 2, factory.newViewOnly(owner, Placeholders.craftingOutput));
slots.set(startIndex + 11, factory.newCraftingResult(owner));
}
return startIndex + listSize;
}
public Slot getMenuSlot(int index, int x, int y) {
return slots.get(index).asSlot(this, index, x, y);
}
public InventoryType.SlotType getSlotType(int index) {
return slots.get(index).getSlotType();
}
public abstract Component getTitle(ServerPlayer player, @Nullable OpenChestMenu<?> menu);
@Override
public ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public org.bukkit.inventory.Inventory getBukkitInventory() {
if (bukkitEntity == null) {
bukkitEntity = new OpenPlayerInventory(this);
}
return bukkitEntity;
}
@Override
public void setPlayerOnline(org.bukkit.entity.Player player) {
ServerPlayer newOwner = PlayerManager.getHandle(player);
// Only transfer regular inventory - crafting and cursor slots are transient.
newOwner.getInventory().replaceWith(owner.getInventory());
owner = newOwner;
// Update slots to point to new inventory.
slots.forEach(slot -> slot.setHolder(newOwner));
}
@Override
public boolean isInUse() {
return !transaction.isEmpty();
}
@Override
public org.bukkit.entity.Player getPlayer() {
return getOwner();
}
@Override
public int getContainerSize() {
return size;
}
@Override
public boolean isEmpty() {
return slots.stream().map(Content::get).allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return slots.get(index).get();
}
@Override
public ItemStack removeItem(int index, int amount) {
return slots.get(index).removePartial(amount);
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return slots.get(index).remove();
}
@Override
public void setItem(int index, ItemStack itemStack) {
slots.get(index).set(itemStack);
}
@Override
public int getMaxStackSize() {
return maxStackSize;
}
@Override
public void setMaxStackSize(int maxStackSize) {
this.maxStackSize = maxStackSize;
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
NonNullList<ItemStack> contents = NonNullList.withSize(getContainerSize(), ItemStack.EMPTY);
for (int i = 0; i < getContainerSize(); ++i) {
contents.set(i, getItem(i));
}
return contents;
}
@Override
public void onOpen(CraftHumanEntity viewer) {
transaction.add(viewer);
}
@Override
public void onClose(CraftHumanEntity viewer) {
transaction.remove(viewer);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return owner.getBukkitEntity();
}
@Override
public Location getLocation() {
return owner.getBukkitEntity().getLocation();
}
@Override
public void clearContent() {
owner.getInventory().clearContent();
owner.inventoryMenu.getCraftSlots().clearContent();
owner.inventoryMenu.slotsChanged(owner.inventoryMenu.getCraftSlots());
owner.containerMenu.setCarried(ItemStack.EMPTY);
}
public @Nullable OpenChestMenu<?> createMenu(Player player, int i, boolean viewOnly) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenInventoryMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,214 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container;
import com.lishid.openinv.internal.ISpecialEnderChest;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenEnderChestMenu;
import com.lishid.openinv.internal.paper26_3.player.PlayerManager;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.item.ItemStack;
import org.bukkit.Location;
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@NullMarked
public class OpenEnderChest implements Container, StackedContentsCompatible, InternalOwned<ServerPlayer>,
ISpecialEnderChest {
private @Nullable CraftInventory inventory;
private ServerPlayer owner;
private NonNullList<ItemStack> items;
private int maxStack = 64;
private final List<HumanEntity> transaction = new ArrayList<>();
public OpenEnderChest(org.bukkit.entity.Player player) {
this.owner = PlayerManager.getHandle(player);
this.items = getEnderChestItems(owner);
}
@Override
public ServerPlayer getOwnerHandle() {
return owner;
}
@Override
public org.bukkit.inventory.Inventory getBukkitInventory() {
if (inventory == null) {
inventory = new CraftInventory(this) {
@Override
public InventoryType getType() {
return InventoryType.ENDER_CHEST;
}
};
}
return inventory;
}
@Override
public void setPlayerOnline(org.bukkit.entity.Player player) {
owner = PlayerManager.getHandle(player);
NonNullList<ItemStack> activeItems = getEnderChestItems(owner);
// Guard against size changing. Theoretically on Purpur all row variations still have 6 rows internally.
int max = Math.min(items.size(), activeItems.size());
for (int index = 0; index < max; ++index) {
activeItems.set(index, items.get(index));
}
items = activeItems;
}
protected NonNullList<ItemStack> getEnderChestItems(ServerPlayer owner) {
return owner.getEnderChestInventory().getItems();
}
@Override
public org.bukkit.entity.Player getPlayer() {
return owner.getBukkitEntity();
}
@Override
public int getContainerSize() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.stream().allMatch(ItemStack::isEmpty);
}
@Override
public ItemStack getItem(int index) {
return index >= 0 && index < items.size() ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItem(int index, int amount) {
ItemStack itemstack = ContainerHelper.removeItem(items, index, amount);
if (!itemstack.isEmpty()) {
setChanged();
}
return itemstack;
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return index >= 0 && index < items.size() ? items.set(index, ItemStack.EMPTY) : ItemStack.EMPTY;
}
@Override
public void setItem(int index, ItemStack itemStack) {
if (index >= 0 && index < items.size()) {
items.set(index, itemStack);
}
}
@Override
public int getMaxStackSize() {
return maxStack;
}
@Override
public void setChanged() {
this.owner.getEnderChestInventory().setChanged();
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public List<ItemStack> getContents() {
return items;
}
@Override
public void onOpen(CraftHumanEntity craftHumanEntity) {
transaction.add(craftHumanEntity);
}
@Override
public void onClose(CraftHumanEntity craftHumanEntity) {
transaction.remove(craftHumanEntity);
}
@Override
public List<HumanEntity> getViewers() {
return transaction;
}
@Override
public org.bukkit.entity.Player getOwner() {
return getPlayer();
}
@Override
public void setMaxStackSize(int size) {
maxStack = size;
}
@Override
public @Nullable Location getLocation() {
return null;
}
@Override
public void clearContent() {
items.clear();
setChanged();
}
@Override
public void fillStackedContents(StackedItemContents stackedContents) {
for (ItemStack itemstack : items) {
stackedContents.accountStack(itemstack);
}
}
public Component getTitle(@Nullable OpenChestMenu<?> menu) {
MutableComponent component;
if (menu != null && menu.isViewOnly()) {
component = Component.translatableWithFallback("openinv.container.enderchest.viewonly", "[RO] ");
} else {
component = Component.translatableWithFallback("openinv.container.enderchest.editable", "");
}
return component
.append(Component.translatableWithFallback("openinv.container.enderchest.prefix", "", owner.getName()))
.append(Component.translatable("container.enderchest"))
.append(Component.translatableWithFallback("openinv.container.enderchest.suffix", " - %s", owner.getName()));
}
public @Nullable OpenChestMenu<?> createMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player player,
int i,
boolean viewOnly
) {
if (player instanceof ServerPlayer serverPlayer) {
return new OpenEnderChestMenu(factory, this, serverPlayer, i, viewOnly);
}
return null;
}
}
@@ -1,58 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FontDescription;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerPlayer;
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.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenInventory extends BaseOpenInventory {
public OpenInventory(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
Player bukkitPlayer
) {
super(factory, bukkitPlayer);
}
@Override
public Component getTitle(@Nullable ServerPlayer viewer, @Nullable OpenChestMenu<?> menu) {
MutableComponent component = Component.empty();
// Prefix for use with custom bitmap image fonts.
if (owner.equals(viewer)) {
component.append(
Component.translatableWithFallback("openinv.container.inventory.self", "")
.withStyle(style -> style
.withFont(new FontDescription.Resource(Identifier.parse("openinv:font/inventory")))
.withColor(ChatFormatting.WHITE)));
} else {
component.append(
Component.translatableWithFallback("openinv.container.inventory.other", "")
.withStyle(style -> style
.withFont(new FontDescription.Resource(Identifier.parse("openinv:font/inventory")))
.withColor(ChatFormatting.WHITE)));
}
if (menu != null && menu.isViewOnly()) {
component.append(Component.translatableWithFallback("openinv.container.inventory.viewonly", "[RO] "));
} else {
component.append(Component.translatableWithFallback("openinv.container.inventory.editable", ""));
}
// Normal title: "Inventory - OwnerName"
component.append(Component.translatableWithFallback("openinv.container.inventory.prefix", "", owner.getName()))
.append(Component.translatable("container.inventory"))
.append(Component.translatableWithFallback("openinv.container.inventory.suffix", " - %s", owner.getName()));
return component;
}
}
@@ -1,174 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.github.jikoo.openinv.internal.container.ViewOnly;
import net.minecraft.world.Container;
import org.bukkit.Material;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.ListIterator;
/**
* A locked down "empty" inventory that rejects plugin interaction.
*/
@NullMarked
public class OpenDummyInventory extends CraftInventory implements ViewOnly {
private final InventoryType type;
public OpenDummyInventory(Container inventory, InventoryType type) {
super(inventory);
this.type = type;
}
@Override
public InventoryType getType() {
return type;
}
@Override
public @Nullable ItemStack getItem(int index) {
return null;
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> addItem(ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> removeItem(ItemStack... items) throws IllegalArgumentException {
return arrayToHashMap(items);
}
@SuppressWarnings("NonApiType")
private static HashMap<Integer, ItemStack> arrayToHashMap(ItemStack[] items) {
HashMap<Integer, ItemStack> ignored = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
ignored.put(index, items[index]);
}
return ignored;
}
@Override
public ItemStack[] getContents() {
return new ItemStack[getSize()];
}
@Override
public void setContents(ItemStack[] items) throws IllegalArgumentException {
}
@Override
public ItemStack[] getStorageContents() {
return new ItemStack[getSize()];
}
@Override
public void setStorageContents(ItemStack[] items) throws IllegalArgumentException {
}
@Override
public boolean contains(Material material) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item) {
return false;
}
@Override
public boolean contains(Material material, int amount) throws IllegalArgumentException {
return false;
}
@Override
public boolean contains(@Nullable ItemStack item, int amount) {
return false;
}
@Override
public boolean containsAtLeast(@Nullable ItemStack item, int amount) {
return false;
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> all(
Material material
) throws IllegalArgumentException {
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> all(@Nullable ItemStack item) {
return new HashMap<>();
}
@Override
public int first(Material material) throws IllegalArgumentException {
return -1;
}
@Override
public int first(ItemStack item) {
return -1;
}
@Override
public int firstEmpty() {
return -1;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void remove(Material material) throws IllegalArgumentException {
}
@Override
public void remove(ItemStack item) {
}
@Override
public void clear(int index) {
}
@Override
public void clear() {
}
@Override
public ListIterator<ItemStack> iterator() {
return Collections.emptyListIterator();
}
@Override
public ListIterator<ItemStack> iterator(int index) {
return Collections.emptyListIterator();
}
}
@@ -1,137 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import net.minecraft.world.Container;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenDummyPlayerInventory extends OpenDummyInventory implements PlayerInventory {
public OpenDummyPlayerInventory(Container inventory) {
super(inventory, InventoryType.PLAYER);
}
@Override
public HumanEntity getHolder() {
return (HumanEntity) super.getHolder();
}
@Override
public ItemStack[] getArmorContents() {
return new ItemStack[4];
}
@Override
public ItemStack[] getExtraContents() {
return new ItemStack[4];
}
@Override
public ItemStack getHelmet() {
return ItemStack.empty();
}
@Override
public ItemStack getChestplate() {
return ItemStack.empty();
}
@Override
public ItemStack getLeggings() {
return ItemStack.empty();
}
@Override
public ItemStack getBoots() {
return ItemStack.empty();
}
@Override
public void setItem(EquipmentSlot slot, @Nullable ItemStack item) {
}
@Override
public ItemStack getItem(EquipmentSlot slot) {
return ItemStack.empty();
}
@Override
public void setArmorContents(@Nullable ItemStack[] items) {
}
@Override
public void setExtraContents(@Nullable ItemStack[] items) {
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
}
@Override
public void setBoots(@Nullable ItemStack boots) {
}
@Override
public ItemStack getItemInMainHand() {
return ItemStack.empty();
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
}
@Override
public ItemStack getItemInOffHand() {
return ItemStack.empty();
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public ItemStack getItemInHand() {
return ItemStack.empty();
}
@Deprecated
@Override
public void setItemInHand(@Nullable ItemStack stack) {
}
@Override
public int getHeldItemSlot() {
return 0;
}
@Override
public void setHeldItemSlot(int slot) {
}
}
@@ -1,218 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.google.common.base.Preconditions;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.List;
@NullMarked
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(BaseOpenInventory inventory) {
super(inventory);
}
@Override
public BaseOpenInventory getInventory() {
return (BaseOpenInventory) super.getInventory();
}
@Override
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
}
@Override
public void setContents(@Nullable ItemStack[] items) {
Inventory internal = getInventory().getOwnerHandle().getInventory();
int size = internal.getContainerSize();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < size; ++index) {
if (index < items.length) {
internal.setItem(index, CraftItemStack.asNMSCopy(items[index]));
} else {
internal.setItem(index, net.minecraft.world.item.ItemStack.EMPTY);
}
}
}
@Override
public ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems());
}
@Override
public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgumentException {
NonNullList<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems();
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
list.set(index, CraftItemStack.asNMSCopy(items[index]));
}
}
@Override
public InventoryType getType() {
return InventoryType.PLAYER;
}
@Override
public Player getHolder() {
return getInventory().getOwner();
}
@Override
public ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getArmorContents());
}
@Override
public void setArmorContents(@Nullable ItemStack[] items) {
int size = Inventory.EQUIPMENT_SLOTS_SORTED_BY_INDEX.length;
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
for (int index = 0; index < items.length; ++index) {
getInventory().getOwnerHandle().getInventory().equipment.set(
Inventory.EQUIPMENT_SLOTS_SORTED_BY_INDEX[index],
CraftItemStack.asNMSCopy(items[index])
);
}
}
@Override
public ItemStack[] getExtraContents() {
return asCraftMirror(List.of(getInventory().getOwnerHandle().getInventory().equipment.get(EquipmentSlot.OFFHAND)));
}
@Override
public void setExtraContents(@Nullable ItemStack[] items) {
Preconditions.checkArgument(items.length <= 1, "items.length must be <= 1");
for (ItemStack item : items) {
getInventory().getOwnerHandle().getInventory().equipment.set(EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(item));
}
}
@Override
public ItemStack getHelmet() {
return getInventory().getOwner().getInventory().getHelmet();
}
@Override
public void setHelmet(@Nullable ItemStack helmet) {
getInventory().getOwner().getInventory().setHelmet(helmet);
}
@Override
public ItemStack getChestplate() {
return getInventory().getOwner().getInventory().getChestplate();
}
@Override
public void setChestplate(@Nullable ItemStack chestplate) {
getInventory().getOwner().getInventory().setChestplate(chestplate);
}
@Override
public ItemStack getLeggings() {
return getInventory().getOwner().getInventory().getLeggings();
}
@Override
public void setLeggings(@Nullable ItemStack leggings) {
getInventory().getOwner().getInventory().setLeggings(leggings);
}
@Override
public ItemStack getBoots() {
return getInventory().getOwner().getInventory().getBoots();
}
@Override
public void setBoots(@Nullable ItemStack boots) {
getInventory().getOwner().getInventory().setBoots(boots);
}
@Override
public ItemStack getItemInMainHand() {
return getInventory().getOwner().getInventory().getItemInMainHand();
}
@Override
public void setItemInMainHand(@Nullable ItemStack item) {
getInventory().getOwner().getInventory().setItemInMainHand(item);
}
@Override
public ItemStack getItemInOffHand() {
return getInventory().getOwner().getInventory().getItemInOffHand();
}
@Override
public void setItemInOffHand(@Nullable ItemStack item) {
getInventory().getOwner().getInventory().setItemInOffHand(item);
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public ItemStack getItemInHand() {
return getItemInMainHand();
}
@SuppressWarnings("InlineMeSuggester")
@Deprecated
@Override
public void setItemInHand(@Nullable ItemStack stack) {
setItemInMainHand(stack);
}
@Override
public int getHeldItemSlot() {
Inventory internal = getInventory().getOwnerHandle().getInventory();
return internal.getNonEquipmentItems().size() - 9 + internal.getSelectedSlot();
}
@Override
public void setHeldItemSlot(int slot) {
slot %= 9;
getInventory().getOwnerHandle().getInventory().setSelectedSlot(slot);
}
@Override
public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) {
return switch (slot) {
case HAND -> getItemInMainHand();
case OFF_HAND -> getItemInOffHand();
case FEET -> getBoots();
case LEGS -> getLeggings();
case CHEST -> getChestplate();
case HEAD -> getHelmet();
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
};
}
@Override
public void setItem(org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) {
switch (slot) {
case HAND -> setItemInMainHand(item);
case OFF_HAND -> setItemInOffHand(item);
case FEET -> setBoots(item);
case LEGS -> setLeggings(item);
case CHEST -> setChestplate(item);
case HEAD -> setHelmet(item);
default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot);
}
}
}
@@ -1,28 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.bukkit;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class OpenPlayerInventorySelf extends OpenPlayerInventory {
private final int offset;
public OpenPlayerInventorySelf(BaseOpenInventory inventory, int offset) {
super(inventory);
this.offset = offset;
}
@Override
public @Nullable ItemStack getItem(int index) {
return super.getItem(offset + index);
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
super.setItem(offset + index, item);
}
}
@@ -1,273 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.google.common.base.Preconditions;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenDummyPlayerInventory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenPlayerInventorySelf;
import com.lishid.openinv.internal.paper26_3.container.slot.ContentDrop;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotEquipment;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import com.lishid.openinv.util.Permissions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
private int offset;
public BaseOpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(getMenuType(inventory, viewer), i, factory, inventory, viewer, viewOnly);
}
private static MenuType<ChestMenu> getMenuType(BaseOpenInventory inventory, ServerPlayer viewer) {
int size = inventory.getContainerSize();
// Disallow duplicate access to own main inventory contents.
if (inventory.getOwnerHandle().equals(viewer)) {
size -= viewer.getInventory().getNonEquipmentItems().size();
size = ((int) Math.ceil(size / 9.0)) * 9;
}
return getChestMenuType(size);
}
@Override
protected void preSlotSetup() {
offset = ownContainer ? viewer.getInventory().getNonEquipmentItems().size() : 0;
}
@Override
protected Slot getUpperSlot(int index, int x, int y) {
index += offset;
Slot slot = container.getMenuSlot(index, x, y);
// If the slot cannot be interacted with there's nothing to configure.
if (slot.getClass().equals(SlotViewOnly.class)) {
return slot;
}
// Remove drop slot if viewer is not allowed to use it.
if (slot instanceof ContentDrop.SlotDrop
&& (viewOnly || !Permissions.INVENTORY_SLOT_DROP.hasPermission(viewer.getBukkitEntity()))) {
return new SlotViewOnly(container, index, x, y);
}
if (slot instanceof SlotEquipment equipment) {
if (viewOnly) {
return factory.wrapViewOnly(slot);
}
Permissions perm = switch (equipment.getEquipmentSlot()) {
case HEAD -> Permissions.INVENTORY_SLOT_HEAD_ANY;
case CHEST -> Permissions.INVENTORY_SLOT_CHEST_ANY;
case LEGS -> Permissions.INVENTORY_SLOT_LEGS_ANY;
case FEET -> Permissions.INVENTORY_SLOT_FEET_ANY;
// Off-hand can hold anything, not just equipment.
default -> null;
};
// If the viewer doesn't have permission, only allow equipment the viewee can equip in the slot.
if (perm != null && !perm.hasPermission(viewer.getBukkitEntity())) {
equipment.onlyEquipmentFor(container.getOwnerHandle());
}
// Equipment slots are a core part of the inventory, so they will always be shown.
return slot;
}
// When viewing own inventory, only allow access to equipment and drop slots (equipment allowed above).
if (ownContainer && !(slot instanceof ContentDrop.SlotDrop)) {
return new SlotViewOnly(container, index, x, y);
}
if (viewOnly) {
return factory.wrapViewOnly(slot);
}
return slot;
}
@Override
protected CraftInventoryView<OpenChestMenu<BaseOpenInventory>, Inventory> createBukkitEntity() {
org.bukkit.inventory.Inventory bukkitInventory;
if (viewOnly) {
bukkitInventory = new OpenDummyPlayerInventory(container);
} else if (ownContainer) {
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
} else {
bukkitInventory = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), bukkitInventory, this) {
@Override
public org.bukkit.inventory.@Nullable ItemStack getItem(int index) {
if (viewOnly || index < 0) {
return null;
}
Slot slot = slots.get(index);
return factory.asBukkitMirror(slot.hasItem() ? slot.getItem() : ItemStack.EMPTY);
}
@Override
public boolean isInTop(int rawSlot) {
return rawSlot < topSize;
}
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
if (rawSlot == InventoryView.OUTSIDE || rawSlot == -1) {
return null;
}
Preconditions.checkArgument(
rawSlot >= 0 && rawSlot < topSize + offset + BOTTOM_INVENTORY_SIZE,
"Slot %s outside of inventory",
rawSlot
);
if (rawSlot > topSize) {
return getBottomInventory();
}
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return null;
}
return getTopInventory();
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
if (rawSlot < 0) {
return rawSlot;
}
if (rawSlot < topSize) {
Slot slot = slots.get(rawSlot);
if (slot.isFake()) {
return InventoryView.OUTSIDE;
}
return rawSlot;
}
int slot = rawSlot - topSize;
if (slot >= 27) {
slot -= 27;
} else {
slot += 9;
}
return slot;
}
@Override
public InventoryType.SlotType getSlotType(int slot) {
if (viewOnly || slot < 0) {
return InventoryType.SlotType.OUTSIDE;
}
if (slot >= topSize) {
slot -= topSize;
if (slot >= 27) {
return InventoryType.SlotType.QUICKBAR;
}
return InventoryType.SlotType.CONTAINER;
}
return BaseOpenInventoryMenu.this.container.getSlotType(offset + slot);
}
@Override
public int countSlots() {
return topSize + BOTTOM_INVENTORY_SIZE;
}
};
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu and InventoryMenu
Slot slot = this.slots.get(index);
if (!slot.hasItem() || slot.isFake()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack originalStack = itemStack.copy();
if (index < topSize) {
// If we're moving top to bottom, do a normal transfer.
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else {
EquipmentSlot equipmentSlot = player.getEquipmentSlotForItem(itemStack);
boolean movedGear = switch (equipmentSlot) {
// If this is gear, try to move it to the correct slot first.
case OFFHAND, FEET, LEGS, CHEST, HEAD -> {
// Locate the correct slot in the contents following the main inventory.
for (int extra = container.getOwnerHandle().getInventory().getNonEquipmentItems().size() - offset; extra < topSize; ++extra) {
Slot extraSlot = getSlot(extra);
if (extraSlot instanceof SlotEquipment equipSlot
&& equipSlot.getEquipmentSlot() == equipmentSlot) {
// If we've found a matching slot, try to move to it.
// If this succeeds, even partially, we will not attempt to move to other slots.
// Otherwise, armor is already occupied, so we'll fall through to main inventory.
yield this.moveItemStackTo(itemStack, extra, extra + 1, false);
}
}
yield false;
}
// Non-gear gets no special treatment.
default -> false;
};
// If main inventory is not available, there's nowhere else to move.
if (offset != 0) {
if (!movedGear) {
return ItemStack.EMPTY;
}
} else {
// If we didn't move to a gear slot, try to move to a main inventory slot.
if (!movedGear && !this.moveItemStackTo(itemStack, 0, container.getOwnerHandle().getInventory().getNonEquipmentItems().size(), true)) {
return ItemStack.EMPTY;
}
}
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return originalStack;
}
}
@@ -1,281 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.lishid.openinv.internal.ISpecialInventory;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.bukkit.OpenDummyInventory;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotViewOnly;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* An extension of {@link AbstractContainerMenu} storing and managing data common to all special inventories.
*/
@NullMarked
public abstract class OpenChestMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends AbstractContainerMenu {
protected static final int BOTTOM_INVENTORY_SIZE = 36;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected final T container;
protected final ServerPlayer viewer;
protected final boolean viewOnly;
protected final boolean ownContainer;
protected final int topSize;
private @Nullable CraftInventoryView<OpenChestMenu<T>, Inventory> bukkitEntity;
protected OpenChestMenu(
MenuType<ChestMenu> type,
int containerCounter,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter);
this.factory = factory;
this.container = container;
this.viewer = viewer;
this.viewOnly = viewOnly;
ownContainer = container.getOwnerHandle().equals(viewer);
topSize = getTopSize(viewer);
preSlotSetup();
int upperRows = topSize / 9;
// View's upper inventory - our container
for (int row = 0; row < upperRows; ++row) {
for (int col = 0; col < 9; ++col) {
// x and y for client purposes, but hey, we're thorough here.
// Adapted from net.minecraft.world.inventory.ChestMenu
int x = 8 + col * 18;
int y = 18 + row * 18;
int index = row * 9 + col;
// Guard against weird inventory sizes.
if (index >= container.getContainerSize()) {
addSlot(new SlotViewOnly(container, index, x, y));
continue;
}
Slot slot = getUpperSlot(index, x, y);
addSlot(slot);
}
}
// View's lower inventory - viewer inventory
int playerInvPad = (upperRows - 4) * 18;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + row * 18 + 103;
addSlot(new Slot(viewer.getInventory(), row * 9 + col + 9, x, y));
}
}
// Hotbar
for (int col = 0; col < 9; ++col) {
int x = 8 + col * 18;
int y = playerInvPad + 161;
addSlot(new Slot(viewer.getInventory(), col, x, y));
}
}
public static MenuType<ChestMenu> getChestMenuType(int inventorySize) {
inventorySize = ((int) Math.ceil(inventorySize / 9.0)) * 9;
return switch (inventorySize) {
case 9 -> MenuType.GENERIC_9x1;
case 18 -> MenuType.GENERIC_9x2;
case 27 -> MenuType.GENERIC_9x3;
case 36 -> MenuType.GENERIC_9x4;
case 45 -> MenuType.GENERIC_9x5;
case 54 -> MenuType.GENERIC_9x6;
default -> throw new IllegalArgumentException("Inventory size unsupported: " + inventorySize);
};
}
protected void preSlotSetup() {
}
protected Slot getUpperSlot(int index, int x, int y) {
Slot slot = new Slot(container, index, x, y);
if (viewOnly) {
return factory.wrapViewOnly(slot);
}
return slot;
}
public boolean isViewOnly() {
return viewOnly;
}
@Override
public final CraftInventoryView<OpenChestMenu<T>, Inventory> getBukkitView() {
if (bukkitEntity == null) {
bukkitEntity = createBukkitEntity();
}
return bukkitEntity;
}
protected CraftInventoryView<OpenChestMenu<T>, Inventory> createBukkitEntity() {
Inventory top;
if (viewOnly) {
top = new OpenDummyInventory(container, container.getBukkitType());
} else {
top = container.getBukkitInventory();
}
return new CraftInventoryView<>(viewer.getBukkitEntity(), top, this) {
@Override
public @Nullable Inventory getInventory(int rawSlot) {
if (viewOnly) {
return null;
}
return super.getInventory(rawSlot);
}
@Override
public int convertSlot(int rawSlot) {
if (viewOnly) {
return InventoryView.OUTSIDE;
}
return super.convertSlot(rawSlot);
}
@Override
public InventoryType.SlotType getSlotType(int slot) {
if (viewOnly) {
return InventoryType.SlotType.OUTSIDE;
}
return super.getSlotType(slot);
}
};
}
private int getTopSize(ServerPlayer viewer) {
MenuType<?> menuType = getType();
if (MenuType.GENERIC_9x1.equals(menuType)) {
return 9;
} else if (MenuType.GENERIC_9x2.equals(menuType)) {
return 18;
} else if (MenuType.GENERIC_9x3.equals(menuType)) {
return 27;
} else if (MenuType.GENERIC_9x4.equals(menuType)) {
return 36;
} else if (MenuType.GENERIC_9x5.equals(menuType)) {
return 45;
} else if (MenuType.GENERIC_9x6.equals(menuType)) {
return 54;
}
// This is a bit gross, but allows us a safe fallthrough.
return menuType.create(-1, viewer.getInventory()).slots.size() - BOTTOM_INVENTORY_SIZE;
}
/**
* Reimplementation of {@link AbstractContainerMenu#moveItemStackTo(ItemStack, int, int, boolean)} that ignores fake
* slots and respects {@link Slot#hasItem()}.
*
* @param itemStack the stack to quick-move
* @param rangeLow the start of the range of slots that can be moved to, inclusive
* @param rangeHigh the end of the range of slots that can be moved to, exclusive
* @param topDown whether to start at the top of the range or bottom
* @return whether the stack was modified as a result of being quick-moved
*/
@Override
protected boolean moveItemStackTo(ItemStack itemStack, int rangeLow, int rangeHigh, boolean topDown) {
boolean modified = false;
boolean stackable = itemStack.isStackable();
Slot firstEmpty = null;
for (int index = topDown ? rangeHigh - 1 : rangeLow;
!itemStack.isEmpty() && (topDown ? index >= rangeLow : index < rangeHigh);
index += topDown ? -1 : 1
) {
Slot slot = slots.get(index);
// If the slot cannot be added to, check the next slot.
if (slot.isFake() || !slot.mayPlace(itemStack)) {
continue;
}
if (slot.hasItem()) {
// If the item isn't stackable, check the next slot.
if (!stackable) {
continue;
}
// Otherwise, add as many as we can from our stack to the slot.
modified |= addToExistingStack(itemStack, slot);
} else {
// If this is the first empty slot, keep track of it for later use.
if (firstEmpty == null) {
firstEmpty = slot;
}
// If the item isn't stackable, we've located the slot we're adding it to, so we're done.
if (!stackable) {
break;
}
}
}
// If the item hasn't been fully added yet, add as many as we can to the first open slot.
if (!itemStack.isEmpty() && firstEmpty != null) {
firstEmpty.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), firstEmpty.getMaxStackSize(itemStack))));
firstEmpty.setChanged();
modified = true;
}
return modified;
}
private static boolean addToExistingStack(ItemStack itemStack, Slot slot) {
ItemStack existing = slot.getItem();
// If the items aren't the same, we can't add our item.
if (!ItemStack.isSameItemSameComponents(itemStack, existing)) {
return false;
}
int max = slot.getMaxStackSize(existing);
int existingCount = existing.getCount();
// If the stack is already full, we can't add more.
if (existingCount >= max) {
return false;
}
int total = existingCount + itemStack.getCount();
// If the existing item can accept the entirety of our item, we're done!
if (total <= max) {
itemStack.setCount(0);
existing.setCount(total);
slot.setChanged();
return true;
}
// Otherwise, add as many as we can.
itemStack.shrink(max - existingCount);
existing.setCount(max);
slot.setChanged();
return true;
}
@Override
public boolean stillValid(Player player) {
return true;
}
}
@@ -1,70 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
public OpenEnderChestMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
OpenEnderChest enderChest,
ServerPlayer viewer,
int containerId,
boolean viewOnly
) {
super(getChestMenuType(enderChest.getContainerSize()), containerId, factory, enderChest, viewer, viewOnly);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
if (viewOnly) {
return ItemStack.EMPTY;
}
// See ChestMenu
Slot slot = this.slots.get(index);
if (slot.isFake() || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack itemStack = slot.getItem();
ItemStack original = itemStack.copy();
if (index < topSize) {
if (!this.moveItemStackTo(itemStack, topSize, this.slots.size(), true)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(itemStack, 0, topSize, false)) {
return ItemStack.EMPTY;
}
if (itemStack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
return original;
}
@Override
public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) {
if (viewOnly) {
if (containerInput == ContainerInput.QUICK_CRAFT) {
sendAllDataToRemote();
}
}
super.clicked(slotIndex, buttonNum, containerInput, player);
}
}
@@ -1,37 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenInventoryMenu extends BaseOpenInventoryMenu {
public OpenInventoryMenu(
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
ServerPlayer viewer,
int i,
boolean viewOnly
) {
super(factory, inventory, viewer, i, viewOnly);
}
@Override
public void clicked(int slotIndex, int buttonNum, ContainerInput containerInput, Player player) {
if (viewOnly) {
if (containerInput == ContainerInput.QUICK_CRAFT) {
sendAllDataToRemote();
}
}
super.clicked(slotIndex, buttonNum, containerInput, player);
}
}
@@ -1,169 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.menu;
import com.google.common.base.Suppliers;
import com.lishid.openinv.internal.ISpecialInventory;
import com.github.jikoo.openinv.internal.container.InternalOwned;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.slot.SlotPlaceholder;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.inventory.ContainerSynchronizer;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.RemoteSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* An extension of {@link OpenChestMenu} that supports {@link SlotPlaceholder placeholders}.
*/
@NullMarked
public abstract class OpenSyncMenu<T extends Container & ISpecialInventory & InternalOwned<ServerPlayer>>
extends OpenChestMenu<T> {
// Syncher fields required to send placeholder items.
// All fields intentionally shadow AbstractContainerMenu fields so that warnings
// will be generated if they are no longer necessary.
protected @Nullable ContainerSynchronizer synchronizer;
protected final List<ContainerListener> listeners = new ArrayList<>();
private @Nullable RemoteSlot remoteCarried = RemoteSlot.PLACEHOLDER;
protected boolean suppressRemoteUpdates;
protected OpenSyncMenu(
MenuType<ChestMenu> type,
int containerCounter,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory,
T container,
ServerPlayer viewer,
boolean viewOnly
) {
super(type, containerCounter, factory, container, viewer, viewOnly);
}
@Override
public void broadcastChanges() {
for (int index = 0; index < this.slots.size(); ++index) {
Slot slot = this.slots.get(index);
ItemStack itemstack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
Supplier<ItemStack> supplier = Suppliers.memoize(itemstack::copy);
this.triggerSlotListeners(index, itemstack, supplier);
this.synchronizeSlotToRemote(index, itemstack, supplier);
}
// See private synchronizeCarriedToRemote
if (!suppressRemoteUpdates && !getRemoteCarried().matches(getCarried())) {
this.broadcastCarriedItem();
}
for (int index = 0; index < this.dataSlots.size(); ++index) {
DataSlot dataSlot = this.dataSlots.get(index);
int current = dataSlot.get();
if (dataSlot.checkAndClearUpdateFlag()) {
// See private updateDataSlotListeners
for (ContainerListener containerListener : this.listeners) {
containerListener.dataChanged(this, index, current);
}
}
// See private synchronizeDataSlotToRemote
if (!suppressRemoteUpdates && remoteDataSlots.getInt(index) != current) {
remoteDataSlots.set(index, current);
if (synchronizer != null) {
synchronizer.sendDataChange(this, index, current);
}
}
}
}
@Override
public void sendAllDataToRemote() {
List<ItemStack> contentsCopy = new ArrayList<>();
for (int index = 0; index < slots.size(); ++index) {
Slot slot = slots.get(index);
ItemStack itemStack = slot instanceof SlotPlaceholder placeholder ? placeholder.getOrDefault() : slot.getItem();
contentsCopy.add(itemStack.copy());
this.remoteSlots.get(index).force(itemStack);
}
getRemoteCarried().force(getCarried());
for (int index = 0; index < this.dataSlots.size(); ++index) {
this.remoteDataSlots.set(index, this.dataSlots.get(index).get());
}
if (this.synchronizer != null) {
this.synchronizer.sendInitialData(this, contentsCopy, this.getCarried().copy(), this.remoteDataSlots.toIntArray());
this.synchronizer.sendOffHandSlotChange();
}
}
protected RemoteSlot getRemoteCarried() {
if (remoteCarried != null) {
return remoteCarried;
}
try {
Field remoteCarried = AbstractContainerMenu.class.getDeclaredField("remoteCarried");
remoteCarried.setAccessible(true);
Object slot = remoteCarried.get(this);
if (slot instanceof RemoteSlot remoteSlot) {
this.remoteCarried = remoteSlot;
} else {
this.remoteCarried = RemoteSlot.PLACEHOLDER;
}
} catch (NoSuchFieldException | IllegalAccessException e) {
remoteCarried = RemoteSlot.PLACEHOLDER;
}
return remoteCarried;
}
// Overrides from here on are purely to capture the contents of fields that aren't exposed.
// Note that super calls should likely always be last! Some send data to remotes after updating state.
@Override
public void addSlotListener(ContainerListener containerListener) {
if (!this.listeners.contains(containerListener)) {
this.listeners.add(containerListener);
}
super.addSlotListener(containerListener);
}
@Override
public void setSynchronizer(ContainerSynchronizer containerSynchronizer) {
this.synchronizer = containerSynchronizer;
// Unset carried slot so we re-capture it when sending slots.
// setSynchronizer calls sendAllDataToRemote, so we can't just do the reflection here once after the fact.
this.remoteCarried = null;
super.setSynchronizer(containerSynchronizer);
}
@Override
public void removeSlotListener(ContainerListener containerListener) {
this.listeners.remove(containerListener);
super.removeSlotListener(containerListener);
}
@Override
public void suppressRemoteUpdates() {
this.suppressRemoteUpdates = true;
super.suppressRemoteUpdates();
}
@Override
public void resumeRemoteUpdates() {
this.suppressRemoteUpdates = false;
super.resumeRemoteUpdates();
}
}
@@ -1,131 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.List;
/**
* A slot in a survival crafting inventory. Unavailable when not online in a survival mode.
*/
@NullMarked
public class ContentCrafting implements Content<ServerPlayer, ItemStack, Container, Slot> {
private final int index;
protected ServerPlayer holder;
protected List<ItemStack> items;
protected ContentCrafting(ServerPlayer holder, int index) {
setHolder(holder);
this.index = index;
}
public static boolean isAvailable(ServerPlayer holder) {
// 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();
}
@Override
public void setHolder(ServerPlayer holder) {
this.holder = holder;
// Note: CraftingContainer#getItems is immutable! Be careful with updates.
this.items = holder.inventoryMenu.getCraftSlots().getContents();
}
@Override
public ItemStack get() {
return isAvailable(holder) ? items.get(index) : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
if (!isAvailable(holder)) {
return ItemStack.EMPTY;
}
ItemStack removed = items.remove(index);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public ItemStack removePartial(int amount) {
if (!isAvailable(holder)) {
return ItemStack.EMPTY;
}
ItemStack removed = ContainerHelper.removeItem(items, index, amount);
if (removed.isEmpty()) {
return ItemStack.EMPTY;
}
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
return removed;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable(holder)) {
items.set(index, itemStack);
holder.inventoryMenu.slotsChanged(holder.inventoryMenu.getCraftSlots());
} else {
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCrafting(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return isAvailable(holder) ? InventoryType.SlotType.CRAFTING : InventoryType.SlotType.OUTSIDE;
}
public class SlotCrafting extends SlotPlaceholder {
private SlotCrafting(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return isAvailable(holder) ? items.get(ContentCrafting.this.index) : Placeholders.survivalOnly(holder);
}
@Override
public boolean mayPickup(Player player) {
return isAvailable(holder);
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable(holder);
}
@Override
public boolean hasItem() {
return isAvailable(holder) && super.hasItem();
}
@Override
public boolean isFake() {
return !isAvailable(holder);
}
}
}
@@ -1,49 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
/**
* A slot allowing viewing of the crafting result.
*
* <p>Unmodifiable because I said so. Use your own crafting grid.</p>
*/
@NullMarked
class ContentCraftingResult extends ContentViewOnly {
ContentCraftingResult(ServerPlayer holder) {
super(holder);
}
@Override
public ItemStack get() {
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getResultSlot().getItem();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
if (!ContentCrafting.isAvailable(holder)) {
return Placeholders.survivalOnly(holder);
}
InventoryMenu inventoryMenu = holder.inventoryMenu;
return inventoryMenu.getResultSlot().getItem();
}
};
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.RESULT;
}
}
@@ -1,121 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
/**
* A slot wrapping the active menu's cursor. Unavailable when not online in a survival mode.
*/
@NullMarked
public class ContentCursor implements Content<ServerPlayer, ItemStack, Container, Slot> {
protected ServerPlayer holder;
protected ContentCursor(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return isAvailable() ? holder.containerMenu.getCarried() : ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
ItemStack carried = holder.containerMenu.getCarried();
holder.containerMenu.setCarried(ItemStack.EMPTY);
return carried;
}
@Override
public ItemStack removePartial(int amount) {
ItemStack carried = holder.containerMenu.getCarried();
if (!carried.isEmpty() && carried.getCount() >= amount) {
ItemStack value = carried.split(amount);
if (carried.isEmpty()) {
holder.containerMenu.setCarried(ItemStack.EMPTY);
}
return value;
}
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
if (isAvailable()) {
holder.containerMenu.setCarried(itemStack);
} else {
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
}
protected boolean isAvailable() {
// 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();
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotCursor(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// As close as possible to "not real"
return InventoryType.SlotType.OUTSIDE;
}
public class SlotCursor extends SlotPlaceholder {
private SlotCursor(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
if (!isAvailable()) {
return Placeholders.survivalOnly(holder);
}
ItemStack carried = holder.containerMenu.getCarried();
return carried.isEmpty() ? Placeholders.cursor : carried;
}
@Override
public boolean mayPickup(Player player) {
return isAvailable();
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return isAvailable();
}
@Override
public boolean hasItem() {
return isAvailable() && super.hasItem();
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,92 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
/**
* A fake slot used to drop items. Unavailable offline.
*/
@NullMarked
public class ContentDrop implements Content<ServerPlayer, ItemStack, Container, Slot> {
protected ServerPlayer holder;
protected ContentDrop(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotDrop(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
// Behaves like dropping an item outside the screen, just by the target player.
return InventoryType.SlotType.OUTSIDE;
}
public class SlotDrop extends SlotPlaceholder {
private SlotDrop(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return OpenPlayer.isConnected(holder.connection)
? Placeholders.drop
: Placeholders.blockedOffline;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return OpenPlayer.isConnected(holder.connection);
}
@Override
public boolean hasItem() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
}
@@ -1,75 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EntityEquipment;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
/**
* A slot for equipment that displays placeholders if empty.
*/
@NullMarked
class ContentEquipment implements Content<ServerPlayer, ItemStack, Container, Slot> {
private EntityEquipment equipment;
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
ContentEquipment(ServerPlayer holder, EquipmentSlot equipmentSlot) {
setHolder(holder);
placeholder = switch (equipmentSlot) {
case HEAD -> Placeholders.emptyHelmet;
case CHEST -> Placeholders.emptyChestplate;
case LEGS -> Placeholders.emptyLeggings;
case FEET -> Placeholders.emptyBoots;
default -> Placeholders.emptyOffHand;
};
this.equipmentSlot = equipmentSlot;
}
@Override
public void setHolder(ServerPlayer holder) {
this.equipment = holder.getInventory().equipment;
}
@Override
public ItemStack get() {
return equipment.get(equipmentSlot);
}
@Override
public ItemStack remove() {
return equipment.set(equipmentSlot, ItemStack.EMPTY);
}
@Override
public ItemStack removePartial(int amount) {
ItemStack current = get();
if (!current.isEmpty() && amount > 0) {
return current.split(amount);
}
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
equipment.set(equipmentSlot, itemStack);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y, placeholder, equipmentSlot);
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.ARMOR;
}
}
@@ -1,68 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.List;
/**
* A normal slot backed by an item list.
*/
@NullMarked
abstract class ContentList implements Content<ServerPlayer, ItemStack, Container, Slot> {
private final int index;
private final InventoryType.SlotType slotType;
private List<ItemStack> items;
ContentList(ServerPlayer holder, int index, InventoryType.SlotType slotType) {
this.index = index;
this.slotType = slotType;
setHolder(holder);
}
@Override
public void setHolder(ServerPlayer holder) {
this.items = getItems(holder);
}
protected abstract List<ItemStack> getItems(ServerPlayer holder);
@Override
public ItemStack get() {
return items.get(index);
}
@Override
public ItemStack remove() {
ItemStack removed = items.remove(index);
return removed.isEmpty() ? ItemStack.EMPTY : removed;
}
@Override
public ItemStack removePartial(int amount) {
return ContainerHelper.removeItem(items, index, amount);
}
@Override
public void set(ItemStack itemStack) {
items.set(index, itemStack);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new Slot(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return slotType;
}
}
@@ -1,58 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
/**
* A slot for equipment that updates held items if necessary.
*/
@NullMarked
class ContentOffHand extends ContentEquipment {
private ServerPlayer holder;
ContentOffHand(ServerPlayer holder) {
super(holder, EquipmentSlot.OFFHAND);
this.holder = holder; // Redundant, but silences nullity warning.
}
@Override
public void setHolder(ServerPlayer holder) {
super.setHolder(holder);
this.holder = holder;
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.QUICKBAR;
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotEquipment(container, slot, x, y, Placeholders.emptyOffHand, EquipmentSlot.OFFHAND) {
@Override
public void setChanged() {
if (OpenPlayer.isConnected(holder.connection) && !Objects.equals(holder.containerMenu, holder.inventoryMenu)) {
holder.connection.send(
new ClientboundContainerSetSlotPacket(
holder.inventoryMenu.containerId,
holder.inventoryMenu.incrementStateId(),
InventoryMenu.SHIELD_SLOT,
holder.getOffhandItem()
));
}
}
};
}
}
@@ -1,59 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Prediction;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
/**
* A view-only slot that can't be interacted with.
*/
@NullMarked
public class ContentViewOnly implements Content<ServerPlayer, ItemStack, Container, Slot> {
protected ServerPlayer holder;
protected ContentViewOnly(ServerPlayer holder) {
this.holder = holder;
}
@Override
public void setHolder(ServerPlayer holder) {
this.holder = holder;
}
@Override
public ItemStack get() {
return ItemStack.EMPTY;
}
@Override
public ItemStack remove() {
return ItemStack.EMPTY;
}
@Override
public ItemStack removePartial(int amount) {
return ItemStack.EMPTY;
}
@Override
public void set(ItemStack itemStack) {
this.holder.drop(itemStack, false, Prediction.SERVER_ONLY);
}
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y);
}
@Override
public InventoryType.SlotType getSlotType() {
return InventoryType.SlotType.OUTSIDE;
}
}
@@ -1,115 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.github.jikoo.openinv.internal.container.slot.Content;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import net.minecraft.server.level.ServerPlayer;
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.inventory.CraftItemStack;
import org.bukkit.event.inventory.InventoryType;
import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.function.Function;
@NullMarked
public class OpenInventoryFactory implements InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> {
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCrafting(ServerPlayer holder, int index) {
return new ContentCrafting(holder, index);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCraftingResult(ServerPlayer holder) {
return new ContentCraftingResult(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newCursor(ServerPlayer holder) {
return new ContentCursor(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newDrop(ServerPlayer holder) {
return new ContentDrop(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newEquipment(
ServerPlayer holder,
EquipmentSlot equipmentSlot
) {
return new ContentEquipment(holder, equipmentSlot);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newList(
ServerPlayer holder,
int index,
InventoryType.SlotType slotType,
Function<ServerPlayer, List<ItemStack>> listSupplier
) {
return new ContentList(holder, index, slotType) {
@Override
protected List<ItemStack> getItems(ServerPlayer holder) {
return listSupplier.apply(holder);
}
};
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newOffHand(ServerPlayer holder) {
return new ContentOffHand(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder) {
return new ContentViewOnly(holder);
}
@Override
public Content<ServerPlayer, ItemStack, Container, Slot> newViewOnly(ServerPlayer holder, ItemStack itemStack) {
return new ContentViewOnly(holder) {
@Override
public Slot asSlot(Container container, int slot, int x, int y) {
return new SlotViewOnly(container, slot, x, y) {
@Override
public ItemStack getOrDefault() {
return itemStack;
}
};
}
};
}
@Override
public Slot wrapViewOnly(Slot wrapped) {
SlotViewOnly wrapper;
if (wrapped instanceof SlotPlaceholder placeholder) {
wrapper = new SlotViewOnly(wrapped.container, wrapped.getContainerSlot(), wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return placeholder.getOrDefault();
}
};
} else {
wrapper = new SlotViewOnly(wrapped.container, wrapped.getContainerSlot(), wrapped.x, wrapped.y) {
@Override
public ItemStack getOrDefault() {
return wrapped.getItem();
}
};
}
wrapper.index = wrapped.index;
return wrapper;
}
@Override
public org.bukkit.inventory.ItemStack asBukkitMirror(ItemStack itemStack) {
return CraftItemStack.asBukkitMirror(itemStack);
}
}
@@ -1,49 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class SlotEquipment extends SlotPlaceholder {
private final ItemStack placeholder;
private final EquipmentSlot equipmentSlot;
private @Nullable ServerPlayer viewer;
SlotEquipment(Container container, int index, int x, int y, ItemStack placeholder, EquipmentSlot equipmentSlot) {
super(container, index, x, y);
this.placeholder = placeholder;
this.equipmentSlot = equipmentSlot;
}
@Override
public ItemStack getOrDefault() {
ItemStack itemStack = getItem();
if (!itemStack.isEmpty()) {
return itemStack;
}
return placeholder;
}
public EquipmentSlot getEquipmentSlot() {
return equipmentSlot;
}
public void onlyEquipmentFor(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public boolean mayPlace(ItemStack itemStack) {
if (viewer == null) {
return true;
}
return equipmentSlot == EquipmentSlot.OFFHAND || viewer.getEquipmentSlotForItem(itemStack) == equipmentSlot;
}
}
@@ -1,22 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
/**
* An implementation of a slot as used by a menu that may have fake placeholder items.
*
* <p>Used to prevent plugins (particularly sorting plugins) from adding placeholders to inventories.</p>
*/
@NullMarked
public abstract class SlotPlaceholder extends Slot {
SlotPlaceholder(Container container, int index, int x, int y) {
super(container, index, x, y);
}
public abstract ItemStack getOrDefault();
}
@@ -1,131 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot;
import com.lishid.openinv.internal.paper26_3.container.slot.placeholder.Placeholders;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.Optional;
/**
* A view-only {@link Slot}. "Blank" by default, but can wrap another slot to display its content.
*/
@NullMarked
public class SlotViewOnly extends SlotPlaceholder {
public SlotViewOnly(Container container, int index, int x, int y) {
super(container, index, x, y);
}
@Override
public ItemStack getOrDefault() {
return Placeholders.notSlot;
}
@Override
public void onQuickCraft(ItemStack itemStack1, ItemStack itemStack2) {
}
@Override
public void onTake(Player player, ItemStack itemStack) {
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return false;
}
@Override
public ItemStack getItem() {
return ItemStack.EMPTY;
}
@Override
public boolean hasItem() {
return false;
}
@Override
public void setByPlayer(ItemStack newStack) {
}
@Override
public void setByPlayer(ItemStack newStack, ItemStack oldStack) {
}
@Override
public void set(ItemStack itemStack) {
}
@Override
public void setChanged() {
}
@Override
public int getMaxStackSize() {
return 0;
}
@Override
public int getMaxStackSize(ItemStack itemStack) {
return 0;
}
@Override
public ItemStack remove(int amount) {
return ItemStack.EMPTY;
}
@Override
public boolean mayPickup(Player player) {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public Optional<ItemStack> tryRemove(int var0, int var1, Player player) {
return Optional.empty();
}
@Override
public ItemStack safeTake(int var0, int var1, Player player) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeClone(Player player) {
return ItemStack.EMPTY;
}
@Override
public ItemStack safeInsert(ItemStack itemStack) {
return itemStack;
}
@Override
public ItemStack safeInsert(ItemStack itemStack, int amount) {
return itemStack;
}
@Override
public boolean allowModification(Player player) {
return false;
}
@Override
public boolean isHighlightable() {
return false;
}
@Override
public boolean isFake() {
return true;
}
}
@@ -1,197 +0,0 @@
package com.lishid.openinv.internal.paper26_3.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.nbt.TagParser;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.entity.BannerPattern;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BannerPatterns;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.LinkedHashSet;
import java.util.List;
@NullMarked
public class PlaceholderLoader {
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())
);
Placeholders.BLOCKED_GAME_TYPE.put(type, typeItem);
}
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)));
}
protected ItemStack parse(
@Nullable ConfigurationSection section,
String path,
ItemStack defaultStack
) throws Exception {
if (section == null) {
return defaultStack;
}
String itemText = section.getString(path);
if (itemText == null) {
return defaultStack;
}
CompoundTag compoundTag = TagParser.parseCompoundFully(itemText);
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;
}
private static final CustomModelData DEFAULT_CUSTOM_MODEL_DATA = new CustomModelData(List.of(), List.of(), List.of("openinv:custom"), List.of());
private static final TooltipDisplay HIDE_TOOLTIP = new TooltipDisplay(true, new LinkedHashSet<>());
protected void addModelData(ItemStack itemStack) {
itemStack.set(DataComponents.CUSTOM_MODEL_DATA, DEFAULT_CUSTOM_MODEL_DATA);
}
protected void hideTooltip(ItemStack itemStack) {
itemStack.set(DataComponents.TOOLTIP_DISPLAY, HIDE_TOOLTIP);
}
protected ItemStack defaultCraftingOutput() {
// Crafting table: "Crafting"
ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE);
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("container.crafting"));
addModelData(itemStack);
return itemStack;
}
protected ItemStack defaultCursor() {
// Cursor-like banner with no tooltip
ItemStack itemStack = new ItemStack(getDefaultCursorItem());
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfDiagBottomRight = bannerPatterns.getOrThrow(BannerPatterns.DIAGONAL_RIGHT).value();
BannerPattern downRight = bannerPatterns.getOrThrow(BannerPatterns.STRIPE_DOWNRIGHT).value();
BannerPattern border = bannerPatterns.getOrThrow(BannerPatterns.BORDER).value();
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);
hideTooltip(itemStack);
return itemStack;
}
protected ItemStack defaultDrop() {
// Dropper: "Drop Selected Item"
ItemStack itemStack = new ItemStack(Items.DROPPER);
// Note: translatable component, not keybind component! We want the text identifying the keybind, not the key.
itemStack.set(DataComponents.ITEM_NAME, Component.translatable("key.drop"));
addModelData(itemStack);
return itemStack;
}
protected ItemStack getEmptyArmor(ItemLike item) {
// Inventory-background-grey-ish leather armor with no tooltip
ItemStack itemStack = new ItemStack(item);
DyedItemColor color = new DyedItemColor(0xC8C8C8);
itemStack.set(DataComponents.DYED_COLOR, color);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
protected ItemStack defaultShield() {
// Shield with "missing texture" pattern, magenta and black squares.
ItemStack itemStack = new ItemStack(Items.SHIELD);
itemStack.set(DataComponents.BASE_COLOR, DyeColor.MAGENTA);
RegistryAccess minecraftRegistry = CraftRegistry.getMinecraftRegistry();
Registry<BannerPattern> bannerPatterns = minecraftRegistry.lookupOrThrow(Registries.BANNER_PATTERN);
BannerPattern halfLeft = bannerPatterns.getOrThrow(BannerPatterns.HALF_VERTICAL).value();
BannerPattern topLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_LEFT).value();
BannerPattern topRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_TOP_RIGHT).value();
BannerPattern bottomLeft = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_LEFT).value();
BannerPattern bottomRight = bannerPatterns.getOrThrow(BannerPatterns.SQUARE_BOTTOM_RIGHT).value();
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)
))
);
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
protected ItemStack defaultNotSlot() {
// White pane with no tooltip
ItemStack itemStack = new ItemStack(getDefaultNotSlotItem());
hideTooltip(itemStack);
addModelData(itemStack);
return itemStack;
}
protected Item getDefaultCursorItem() {
return Items.BANNER.white();
}
protected Item getDefaultNotSlotItem() {
return Items.STAINED_GLASS_PANE.white();
}
protected ItemStack defaultBlockedOffline() {
// Barrier: "Not available - Offline"
ItemStack itemStack = new ItemStack(Items.BARRIER);
itemStack.set(DataComponents.ITEM_NAME,
Component.translatable("options.narrator.notavailable")
.append(Component.literal(" - "))
.append(Component.translatable("gui.socialInteractions.status_offline"))
);
return itemStack;
}
}
@@ -1,42 +0,0 @@
package com.lishid.openinv.internal.paper26_3.container.slot.placeholder;
import com.lishid.openinv.internal.paper26_3.player.OpenPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import org.jspecify.annotations.NullMarked;
import java.util.EnumMap;
@NullMarked
public final class Placeholders {
// N.B. Slot#getNoItemIcon is still clientside, unfortunately.
// Worth checking every few updates just in case - it would be nice to
// get rid of some of the placeholders.
static final EnumMap<GameType, ItemStack> BLOCKED_GAME_TYPE = new EnumMap<>(GameType.class);
public static ItemStack craftingOutput = ItemStack.EMPTY;
public static ItemStack cursor = ItemStack.EMPTY;
public static ItemStack drop = ItemStack.EMPTY;
public static ItemStack emptyHelmet = ItemStack.EMPTY;
public static ItemStack emptyChestplate = ItemStack.EMPTY;
public static ItemStack emptyLeggings = ItemStack.EMPTY;
public static ItemStack emptyBoots = ItemStack.EMPTY;
public static ItemStack emptyOffHand = ItemStack.EMPTY;
public static ItemStack notSlot = ItemStack.EMPTY;
public static ItemStack blockedOffline = ItemStack.EMPTY;
public static ItemStack survivalOnly(ServerPlayer serverPlayer) {
if (!OpenPlayer.isConnected(serverPlayer.connection)) {
return blockedOffline;
}
return BLOCKED_GAME_TYPE.getOrDefault(serverPlayer.gameMode.getGameModeForPlayer(), ItemStack.EMPTY);
}
private Placeholders() {
throw new IllegalStateException("Cannot create instance of utility class.");
}
}
@@ -1,224 +0,0 @@
package com.lishid.openinv.internal.paper26_3.player;
import com.lishid.openinv.event.OpenEvents;
import com.mojang.logging.LogUtils;
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.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
@NullMarked
public class OpenPlayer extends CraftPlayer {
/**
* List of tags to always reset when saving. These are items that do not get written
* if unset or empty, resulting in older values not being clobbered appropriately.
*
* @see net.minecraft.world.entity.Entity#saveWithoutId(ValueOutput, boolean, boolean, boolean)
* @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)
"invulnerable_time",
"CustomName",
"CustomNameVisible",
"Silent",
"NoGravity",
"Glowing",
"TicksFrozen",
"HasVisualFire",
"Tags",
"data",
"Passengers",
// ServerPlayer#addAdditionalSaveData(CompoundTag)
// Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle
"entered_nether_pos",
"last_explosion_impact_pos",
"respawn",
"raid_omen_position",
"ender_pearls",
"ShoulderEntityLeft",
"ShoulderEntityRight",
// Player#addAdditionalSaveData(CompoundTag)
"LastDeathLocation",
// LivingEntity#addAdditionalSaveData(CompoundTag)
"current_explosion_impact_pos",
"active_effects",
"sleeping_pos",
"Brain", // No longer needed as of 26.1
"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(server.getServer(), getHandle());
}
@Override
public void saveData() {
if (OpenEvents.saveCancelled(this)) {
return;
}
ServerPlayer player = 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;
CompoundTag oldData;
oldData = isOnline() ? null : worldNbtStorage.load(player.nameAndId()).orElse(null);
CompoundTag playerData = getWritableTag(oldData);
ValueOutput valueOutput = TagValueOutput.createWrappingWithContext(scopedCollector, player.registryAccess(), playerData);
player.saveWithoutId(valueOutput);
saveSafe(player, oldData, playerData, worldNbtStorage);
} catch (Exception e) {
LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e);
}
}
protected void saveSafe(
ServerPlayer player,
@Nullable CompoundTag oldData,
CompoundTag playerData,
PlayerDataStorage worldNbtStorage
) throws IOException {
// 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");
safeReplaceFile(dataFile, tempFile, backupFile);
}
protected void safeReplaceFile(
Path dataFile,
Path tempFile,
Path backupFile
) {
net.minecraft.util.Util.safeReplaceFile(dataFile, tempFile, backupFile);
}
@Contract("null -> new")
protected 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(CompoundTag newData, @Nullable CompoundTag oldData) {
if (oldData == null) {
return;
}
// 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(
CompoundTag source,
CompoundTag target,
String container,
String key,
@SuppressWarnings("SameParameterValue") 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,
String key,
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(
CompoundTag container,
String key,
@Nullable T data
) {
if (data == null) {
remove(container, key);
} else {
container.put(key, data);
}
}
protected void remove(CompoundTag tag, String key) {
tag.remove(key);
}
public static boolean isConnected(@Nullable ServerGamePacketListenerImpl connection) {
return connection != null && !connection.isDisconnected();
}
}
@@ -1,340 +0,0 @@
package com.lishid.openinv.internal.paper26_3.player;
import com.lishid.openinv.internal.ISpecialInventory;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory;
import com.lishid.openinv.internal.paper26_3.container.OpenEnderChest;
import com.lishid.openinv.internal.paper26_3.container.menu.OpenChestMenu;
import com.github.jikoo.openinv.util.JulLoggerAdapter;
import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
import io.papermc.paper.adventure.PaperAdventure;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.server.MinecraftServer;
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.Container;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.ChatVisiblity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelData;
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 net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryView;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
@NullMarked
public class PlayerManager implements com.github.jikoo.openinv.internal.PlayerManager {
protected final Logger logger;
protected final InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory;
protected @Nullable Field bukkitEntity;
public PlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
this.logger = logger;
this.factory = factory;
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
}
public static ServerPlayer getHandle(final Player player) {
if (player instanceof CraftPlayer craftPlayer) {
return craftPlayer.getHandle();
}
Server server = player.getServer();
ServerPlayer nmsPlayer = null;
if (server instanceof CraftServer craftServer) {
nmsPlayer = craftServer.getHandle().getPlayer(player.getUniqueId());
}
if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}
return nmsPlayer;
}
@Override
public @Nullable Player loadPlayer(final OfflinePlayer offline) {
if (!(Bukkit.getServer() instanceof CraftServer craftServer)) {
return null;
}
MinecraftServer server = craftServer.getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);
if (worldServer == null) {
return null;
}
// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);
// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
removeAdvancementListeners(entity);
// Try to load the player's data.
if (loadData(server, entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}
return null;
}
protected ServerPlayer createNewPlayer(
MinecraftServer server,
ServerLevel worldServer,
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()
);
ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
ChatVisiblity.HIDDEN, // Don't accept chat.
false,
ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION,
ServerPlayer.DEFAULT_MAIN_HAND,
true,
false, // Don't list in player list (not that this player is in the list anyway).
ParticleStatus.MINIMAL
);
ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);
try {
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!"
);
}
return entity;
}
protected void removeAdvancementListeners(ServerPlayer entity) {
entity.getAdvancements().clearTriggers();
}
protected boolean loadData(MinecraftServer server, ServerPlayer player) {
// See CraftPlayer#loadData
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), new JulLoggerAdapter(logger))) {
CompoundTag loadedData = server.getPlayerList().playerIo.load(player.nameAndId()).orElse(null);
if (loadedData == null) {
// Exceptions with loading are logged.
return false;
}
ValueInput valueInput = TagValueInput.create(scopedCollector, player.registryAccess(), loadedData);
// Read basic data into the player.
player.load(valueInput);
// World is not loaded by ServerPlayer#load(CompoundTag) on Paper.
parseWorld(server, player, valueInput);
}
return true;
}
protected void parseWorld(
MinecraftServer server,
ServerPlayer player,
ValueInput 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 {
bukkitWorld = loadedData.getString("world").map(Bukkit::getWorld).orElse(null);
}
if (bukkitWorld == null) {
spawnInDefaultWorld(server, player);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}
protected void spawnInDefaultWorld(MinecraftServer server, 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.
LevelData.RespawnData respawnData = getRespawnData(level);
player.snapTo(getAdjustedSpawnLocation(player, level, respawnData), respawnData.yaw(), 0.0F);
setServerLevel(player, level);
} else {
logger.warning("Tried to load player with invalid world when no fallback was available!");
}
}
protected LevelData.RespawnData getRespawnData(ServerLevel level) {
return level.getRespawnData();
}
protected Vec3 getAdjustedSpawnLocation(
ServerPlayer player,
ServerLevel level,
LevelData.RespawnData respawnData
) {
return Vec3.atBottomCenterOf(player.adjustSpawnLocation(level, respawnData.pos()));
}
protected void setServerLevel(ServerPlayer player, ServerLevel level) {
player.setServerLevel(level);
}
protected void injectPlayer(MinecraftServer server, ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
bukkitEntity.setAccessible(true);
bukkitEntity.set(player, new OpenPlayer(server.server, player, this));
}
@Override
public Player inject(Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
return openPlayer;
}
org.slf4j.Logger logger = LogUtils.getLogger();
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(nmsPlayer.problemPath(), logger)) {
CompoundTag extraData = new CompoundTag();
// Copy extra data out of existing player.
ValueOutput output = TagValueOutput.createWrappingWithContext(scopedCollector, nmsPlayer.registryAccess(), extraData);
nmsPlayer.getBukkitEntity().setExtraData(output);
MinecraftServer server = nmsPlayer.level().getServer();
injectPlayer(server, nmsPlayer);
CraftPlayer newPlayer = nmsPlayer.getBukkitEntity();
// Set extra data in new player.
ValueInput input = TagValueInput.create(scopedCollector, nmsPlayer.registryAccess(), extraData);
newPlayer.readExtraData(input);
return newPlayer;
}
} 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
public @Nullable InventoryView openInventory(
Player bukkitPlayer,
ISpecialInventory inventory,
boolean viewOnly
) {
ServerPlayer player = getHandle(bukkitPlayer);
if (!OpenPlayer.isConnected(player.connection)) {
return null;
}
// See net.minecraft.server.level.ServerPlayer#openMenu(MenuProvider)
OpenChestMenu<?> menu;
Component title;
if (inventory instanceof BaseOpenInventory playerInv) {
menu = playerInv.createMenu(player, player.nextContainerCounter(), viewOnly);
title = playerInv.getTitle(player, menu);
} else if (inventory instanceof OpenEnderChest enderChest) {
menu = enderChest.createMenu(factory, player, player.nextContainerCounter(), viewOnly);
title = enderChest.getTitle(menu);
} else {
return null;
}
// Should never happen, player is a ServerPlayer with an active connection.
if (menu == null) {
return null;
}
// Set up title. Title can only be set once for a menu, and is set during the open process.
// Further title changes are a hack where the client is sent a "new" inventory with the same ID,
// resulting in a title change but no other state modifications (like cursor position).
menu.setTitle(title);
var pair = CraftEventFactory.callInventoryOpenEventWithTitle(player, menu);
AbstractContainerMenu opened = pair.getSecond();
// Menu is null if event is cancelled.
if (opened == null) {
return null;
}
var newTitle = pair.getFirst();
if (newTitle != null) {
title = PaperAdventure.asVanilla(newTitle);
}
player.containerMenu = opened;
player.connection.send(new ClientboundOpenScreenPacket(opened.containerId, opened.getType(), title));
player.initMenu(opened);
return opened.getBukkitView();
}
}
-39
View File
@@ -1,39 +0,0 @@
import com.github.jikoo.openinv.SpigotDependencyExtension
import com.github.jikoo.openinv.SpigotSetup
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(25)
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
}
apply<SpigotSetup>()
dependencies {
compileOnly(libs.spigotapi)
extensions.getByType(SpigotDependencyExtension::class.java).version = "26.1.2-R0.1-SNAPSHOT"
compileOnly("com.mojang:logging:1.6.11")
compileOnly("com.mojang:brigadier:1.3.10")
compileOnly("com.mojang:datafixerupper:9.0.19")
compileOnly("com.mojang:authlib:7.0.62")
compileOnly(project(":openinvapi"))
compileOnly(project(":openinvcommon"))
compileOnly(project(":openinvadapterspigot26_2")) {
exclude(group = "org.spigotmc", module = "spigot")
}
compileOnly(project(":openinvadapterspigot26_3")) {
exclude(group = "org.spigotmc", module = "spigot")
}
}
@@ -1,44 +0,0 @@
package com.github.jikoo.openinv.internal.spigot26_1;
import com.github.jikoo.openinv.internal.container.slot.InventoryFactory;
import com.github.jikoo.openinv.internal.spigot26_1.container.slot.placeholder.PlaceholderLoader;
import com.github.jikoo.openinv.internal.spigot26_1.player.PlayerManager;
import com.github.jikoo.openinv.lang.LanguageManager;
import net.minecraft.server.level.ServerPlayer;
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.configuration.ConfigurationSection;
import org.jspecify.annotations.NullMarked;
import java.util.logging.Level;
import java.util.logging.Logger;
@NullMarked
public class InternalAccessor extends com.github.jikoo.openinv.internal.spigot26_2.InternalAccessor {
public InternalAccessor(Logger logger, LanguageManager lang) {
super(logger, lang);
}
@Override
protected PlayerManager createPlayerManager(
Logger logger,
InventoryFactory<ServerPlayer, ItemStack, Container, Slot, EquipmentSlot> factory
) {
return new PlayerManager(logger, factory);
}
@Override
public void reload(ConfigurationSection config) {
ConfigurationSection placeholders = config.getConfigurationSection("placeholders");
try {
// Reset placeholders to defaults and try to load configuration.
new PlaceholderLoader().load(placeholders);
} catch (Exception e) {
logger.log(Level.WARNING, "Caught exception loading placeholder overrides!", e);
}
}
}
Loaded 100 of 327 files, more files were not shown because too many files have changed in this diff. Show more