diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aabf240 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,795 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +#g-crlf +end_of_line = lf +#g-2 +indent_size = 4 +indent_style = space +#g-false +insert_final_newline = true +max_line_length = 100 +#g-2 +tab_width = 4 +#g-4 +ij_continuation_indent_size = 8 +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 + +[*.dcl] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_declarative_keep_indents_on_empty_lines = false + +[*.java] +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 = false +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 = false +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_new_line_in_record_component = false +#g-off +ij_java_annotation_parameter_wrap = split_into_lines +#g-false +ij_java_array_initializer_new_line_after_left_brace = true +#g-false +ij_java_array_initializer_right_brace_on_new_line = true +#g-normal +ij_java_array_initializer_wrap = on_every_item +ij_java_assert_statement_colon_on_next_line = false +#g-off +ij_java_assert_statement_wrap = normal +#g-off +ij_java_assignment_wrap = normal +ij_java_binary_operation_sign_on_next_line = true +ij_java_binary_operation_wrap = normal +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 1 +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 = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_blank_lines_between_record_components = 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 = +#g-false +ij_java_call_parameters_new_line_after_left_paren = true +#g-false +ij_java_call_parameters_right_paren_on_new_line = true +#g-normal +ij_java_call_parameters_wrap = on_every_item +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 +#g-999 +ij_java_class_count_to_use_import_on_demand = 5 +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 +#g-always +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 = true +ij_java_doc_align_param_comments = true +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 +#g-off +ij_java_enum_constants_wrap = split_into_lines +#g-off +ij_java_enum_field_annotation_wrap = split_into_lines +#g-off +ij_java_extends_keyword_wrap = normal +ij_java_extends_list_wrap = normal +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 +#g-always +ij_java_for_brace_force = never +#g-false +ij_java_for_statement_new_line_after_left_paren = true +#g-false +ij_java_for_statement_right_paren_on_new_line = true +#g-normal +ij_java_for_statement_wrap = on_every_item +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +#g-always +ij_java_if_brace_force = never +#g-$*,|,* +ij_java_imports_layout = *, |, javax.**, java.**, |, $* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = true +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 = 1 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = false +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 = false +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 +#g-normal +ij_java_method_call_chain_wrap = on_every_item +#g-false +ij_java_method_parameters_new_line_after_left_paren = true +#g-false +ij_java_method_parameters_right_paren_on_new_line = true +#g-normal +ij_java_method_parameters_wrap = on_every_item +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +#g-999 +ij_java_names_count_to_use_import_on_demand = 3 +#g-false +ij_java_new_line_after_lparen_in_annotation = true +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +#g-false +ij_java_new_line_after_lparen_in_record_header = true +ij_java_new_line_when_body_is_presented = false +#g- +ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* +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 = false +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 +#g-normal +ij_java_record_components_wrap = on_every_item +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +#g-false +ij_java_resource_list_new_line_after_left_paren = true +#g-false +ij_java_resource_list_right_paren_on_new_line = true +#g-off +ij_java_resource_list_wrap = on_every_item +#g-false +ij_java_rparen_on_new_line_in_annotation = true +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +#g-false +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 = true +#g-normal +ij_java_ternary_operation_wrap = on_every_item +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +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 = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = true +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[*.nbtt] +indent_size = 4 +indent_style = tab +max_line_length = 150 +tab_width = 4 +#g-unset +ij_continuation_indent_size = 4 +ij_nbtt_keep_indents_on_empty_lines = false +ij_nbtt_space_after_colon = true +ij_nbtt_space_after_comma = true +ij_nbtt_space_before_colon = true +ij_nbtt_space_before_comma = false +ij_nbtt_spaces_within_brackets = false +ij_nbtt_spaces_within_parentheses = false + +[*.properties] +ij_properties_align_group_field_declarations = false +#g-false +ij_properties_keep_blank_lines = true +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_continuation_indent_size = 2 +ij_xml_align_attributes = false +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}] +#g-unset +indent_size = 2 +#g-unset +tab_width = 2 +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 + +[{*.gant,*.gdsl,*.gradle,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_packages_to_use_import_on_demand = java.awt.*, javax.swing.* +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.har,*.inputactions,*.json,*.jsonc,*.png.mcmeta,mcmod.info,pack.mcmeta}] +#g-unset +indent_size = 2 +#g-unset +tab_width = 2 +ij_json_array_wrapping = split_into_lines +#g-0 +ij_json_keep_blank_lines_in_code = 1 +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 + +[{*.kt,*.kts}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +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 +#s-off +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 +#s-false +ij_kotlin_call_parameters_new_line_after_left_paren = true +#s-false +ij_kotlin_call_parameters_right_paren_on_new_line = true +#s-off +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +#s-true +ij_kotlin_continuation_indent_for_chained_calls = false +#s-true +ij_kotlin_continuation_indent_for_expression_bodies = false +#s-true +ij_kotlin_continuation_indent_in_argument_lists = false +#s-true +ij_kotlin_continuation_indent_in_elvis = false +#s-true +ij_kotlin_continuation_indent_in_if_conditions = false +#s-true +ij_kotlin_continuation_indent_in_parameter_lists = false +#s-true +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +#s-off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +#s-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 +#s-off +ij_kotlin_method_call_chain_wrap = normal +#s-false +ij_kotlin_method_parameters_new_line_after_left_paren = true +#s-false +ij_kotlin_method_parameters_right_paren_on_new_line = true +#s-off +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 +#s-0 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.markdown,*.md}] +#g-4 +indent_size = 2 +#g-4 +tab_width = 2 +#g-8 +ij_continuation_indent_size = 4 +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 +#g-true +ij_markdown_keep_line_breaks_inside_text_blocks = false +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}] +indent_size = 4 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +#g-unset +indent_size = 2 +#g-unset +tab_width = 2 +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 +#g-false +ij_yaml_line_comment_add_space = true +#g-false +ij_yaml_line_comment_add_space_on_reformat = true +#g-true +ij_yaml_line_comment_at_first_column = false +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cc9ea95 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +*.bat text eol=crlf +*.patch text eol=lf +*.java text eol=lf +*.gradle text eol=crlf +*.png binary +*.gif binary +*.exe binary +*.dll binary +*.jar binary +*.lzma binary +*.zip binary +*.pyd binary +*.cfg text eol=lf +*.jks binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..586efe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,126 @@ +name: Mod bug report +description: "For reporting bugs and other defects" +labels: + - S/needs-triage +body: + - type: markdown + attributes: + value: >- + ### ⚠️ Bug Report + + **Thank you for taking the time to make a report!** This form is designed to help you + provide the information necessary for us to understand and fix the issue. Before you start, + please complete the following checks: + + + 1. Are you using the latest version of the mod? If there is a more recent version of the mod + available for your version of Minecraft, please check whether the issue still occurs on that + version. + + + 2. Has the issue already been reported? If it has been, please do not open a new issue, but + if you have additional information please comment on the existing report. + + + 3. Have you determined the minimum set of instructions to reproduce the issue? If the issue + only occurs in specific situations or with other mods installed, please attempt to narrow + down exactly what conditions or mods are required to reproduce it. + + - type: checkboxes + id: preliminary-checks + attributes: + label: Checklist + options: + - label: This issue exists on the latest release for my Minecraft version. + required: true + - label: This issue has not already been reported. + required: true + - label: I have determined the minimal reproduction requirements. + required: true + + - type: input + id: mod-loader + attributes: + label: Mod Loader + description: >- + Which mod loader are you using? + + **Examples:** `Fabric`, `NeoForge` + validations: + required: true + + - type: input + id: mc-version + attributes: + label: Minecraft Version + description: >- + Which version of Minecraft are you using? + + **Examples:** `1.21.4`, `1.20.1` + validations: + required: true + + - type: input + id: mod-version + attributes: + label: Mod Version + description: >- + Which version of this mod are you using? + + **Example:** `2.2.0+1.21.4` + validations: + required: true + + - type: input + id: log-link + attributes: + label: Log File + description: >- + Please upload your log file to [mclo.gs](https://mclo.gs) and paste the link here. Even if + there are no crashes, logs help identify version details and potential error messages. + + + **Hint:** Log files are located in the `logs` folder of your Minecraft instance, typically + `.minecraft/logs`. You will usually want the `latest.log` file, since that file belongs to + the most recent session of the game. Alternatively, some launchers such as Prism support + uploading logs to mclo.gs directly. + placeholder: https://mclo.gs/your-log-link + validations: + required: true + + - type: textarea + id: description + attributes: + label: Issue Description + description: >- + Please describe in detail the issue you are experiencing. If you have any screenshots, + videos, or other resources that help illustrate the problem, you can attach them here. + placeholder: Describe the issue... + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: Steps to Reproduce + description: >- + Please list the steps that we should follow to encounter the issue. If you are unsure, or + unable to consistently reproduce it, please provide a detailed description of what you + were doing when the issue occurred. + + 1. Do X + + 2. Do Y + + 3. Issue occurs + placeholder: List the steps to reproduce the issue... + validations: + required: true + + - type: input + id: expected-behavior + attributes: + label: Expected Behavior (optional) + description: >- + Describe briefly what you believe should happen if the issue was fixed. If the answer is + obvious (such as if the game is crashing), you can skip this. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3fa69ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Support enquiry, question or discussion + url: https://discord.terminalmc.dev + about: For any issue that isn't specifically a bug report or feature request, please use Discord diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..d1480f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,75 @@ +name: Mod feature request +description: "For requesting new features or improvements" +labels: + - S/needs-triage +body: + - type: markdown + attributes: + value: >- + ### 🛠️ Feature Request + + **Thank you for taking the time to suggest a new feature!** This form is designed to help + you provide the information necessary for us to understand and implement the feature. Before + you start, please complete the following checks: + + + 1. Are you requesting either a new feature or an improvement of existing functionality? For + general support, or to request an update or backport, please use + [Discord](https://discord.terminalmc.dev). + + + 2. Have you checked whether the feature already exists on the latest version? If you want to + request that an existing feature be backported, please use + [Discord](https://discord.terminalmc.dev). + + + 3. Has the feature already been requested? If it has been, please comment on the existing + request instead. + + - type: checkboxes + id: preliminary-checks + attributes: + label: Checklist + options: + - label: I am requesting a new feature or a functional improvement. + required: true + - label: This feature does not exist on the latest version. + required: true + - label: This feature has not already been requested. + required: true + + - type: textarea + id: description + attributes: + label: Feature Description + description: >- + Please describe in detail what you would like added or changed, with reasoning. If you have + considered alternative options, include them as well. + + + **Hint:** Requests that lack detail or do not include a clear reason are less likely to be + considered. + placeholder: Describe the feature... + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: >- + Please explain how this change would improve the experience of using the mod, with reference + to specific scenarios or examples as applicable. + placeholder: Describe your use-case... + validations: + required: true + + - type: input + id: mc-version + attributes: + label: Minecraft Version (optional) + description: >- + Feature updates are generally only released for the latest Minecraft version. If you need + the new feature on an older version, list the version(s) here. + + **Examples:** `1.21.4`, `1.20.1` diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..ece9bc4 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,120 @@ +- name: E/duplicate + color: 'BFD4F2' + description: 'Closed: Same as another' + aliases: [] + +- name: E/external + color: 'BFD4F2' + description: 'Closed: External issue' + aliases: [] + +- name: E/invalid + color: 'BFD4F2' + description: 'Closed: Not an issue' + aliases: [] + +- name: E/no-action + color: 'BFD4F2' + description: 'Closed: Not actionable or inadequate information' + aliases: [] + +- name: E/scope + color: 'BFD4F2' + description: 'Closed: Out of scope' + aliases: [] + +- name: E/wontfix + color: 'BFD4F2' + description: 'Closed: Will not be worked on' + aliases: [] + + + +- name: P/high + color: 'B60205' + description: 'Priority: High, for immediate attention' + aliases: [] + +- name: P/low + color: '006B75' + description: 'Priority: Low, not time-sensitive' + aliases: [] + +- name: P/medium + color: '9B5003' + description: 'Priority: Medium, time-sensitive but not urgent' + aliases: [] + + + +- name: S/accepted + color: '172B72' + description: 'Status: Accepted but not assigned' + aliases: [] + +- name: S/blocked + color: '97988B' + description: 'Status: Blocked by another event' + aliases: [] + +- name: S/finished + color: '0E8A16' + description: 'Status: Complete, awaiting release' + aliases: [fixed,complete] + +- name: S/in-progress + color: '36210B' + description: 'Status: Being worked on' + aliases: [] + +- name: S/info-needed + color: '4A4800' + description: 'Status: Awaiting further information' + aliases: [] + +- name: S/needs-triage + color: 'D4C31A' + description: 'Status: Needs triage' + aliases: [] + + + +- name: T/addition + color: '139399' + description: 'Type: New feature' + aliases: [] + +- name: T/bug + color: 'A83400' + description: 'Type: Bug' + aliases: [] + +- name: T/compat + color: '490839' + description: 'Type: Compatibility' + aliases: [] + +- name: T/enhancement + color: '0052CC' + description: 'Type: Enhancement or optimization' + aliases: [] + +- name: T/fix + color: '0E8A16' + description: 'Type: Issue fix' + aliases: [] + +- name: T/port + color: '5319E7' + description: 'Type: Upgrade or downgrade game version' + aliases: [] + +- name: T/security + color: 'B60205' + description: 'Type: Security issue' + aliases: [] + +- name: T/translation + color: '8C02DA' + description: 'Type: Translation' + aliases: [] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 028f069..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,36 +0,0 @@ -on: - push: - tags-ignore: - - 'v*' - branches: - - '*' - pull_request: - -name: Build Mod - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: 17 - - name: Build - uses: gradle/gradle-build-action@v2 - with: - arguments: build --stacktrace - - name: Upload Fabric Artifacts - uses: actions/upload-artifact@v2 - with: - name: Fabric - path: | - fabric/build/libs/*.jar - release/now-playing-fabric*.jar - - name: Upload Forge Artifacts - uses: actions/upload-artifact@v2 - with: - name: Forge - path: | - forge/build/libs/*.jar - release/now-playing-forge*.jar diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml new file mode 100644 index 0000000..aab99b4 --- /dev/null +++ b/.github/workflows/check-build.yml @@ -0,0 +1,51 @@ +# Builds the project, as a first line of defense against bad commits. +name: Check Build + +on: + push: + paths: [ + '**src/**', + '**/*gradle*' + ] + pull_request: + paths: [ + '**src/**', + '**/*gradle*' + ] + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build --stacktrace + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release-platform-curseforge.yml b/.github/workflows/release-platform-curseforge.yml new file mode 100644 index 0000000..2dd4ae8 --- /dev/null +++ b/.github/workflows/release-platform-curseforge.yml @@ -0,0 +1,46 @@ +# A CurseForge-only version of the normal release workflow. +name: Release-Platform-CurseForge + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build publishCurseforge --stacktrace + env: + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release-platform-github.yml b/.github/workflows/release-platform-github.yml new file mode 100644 index 0000000..8e3f1f3 --- /dev/null +++ b/.github/workflows/release-platform-github.yml @@ -0,0 +1,46 @@ +# A GitHub-only version of the normal release workflow. +name: Release-Platform-GitHub + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build publishGithub --stacktrace + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release-platform-modrinth.yml b/.github/workflows/release-platform-modrinth.yml new file mode 100644 index 0000000..1918e43 --- /dev/null +++ b/.github/workflows/release-platform-modrinth.yml @@ -0,0 +1,46 @@ +# A Modrinth-only version of the normal release workflow. +name: Release-Platform-Modrinth + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build publishModrinth --stacktrace + env: + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release-subproject-fabric.yml b/.github/workflows/release-subproject-fabric.yml new file mode 100644 index 0000000..fc5fb0c --- /dev/null +++ b/.github/workflows/release-subproject-fabric.yml @@ -0,0 +1,48 @@ +# A Fabric-only version of the normal release workflow. +name: Release-Subproject-Fabric + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build fabric:publishGithub fabric:publishCurseforge fabric:publishModrinth --stacktrace + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release-subproject-neoforge.yml b/.github/workflows/release-subproject-neoforge.yml new file mode 100644 index 0000000..f7d09bb --- /dev/null +++ b/.github/workflows/release-subproject-neoforge.yml @@ -0,0 +1,48 @@ +# A NeoForge-only version of the normal release workflow. +name: Release-Subproject-NeoForge + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build neoforge:publishGithub neoforge:publishCurseforge neoforge:publishModrinth --stacktrace + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 + with: + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 143fbea..1efbad4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,35 +1,53 @@ +# Builds the project and publishes the artifacts to GitHub, Modrinth and CurseForge. +# Modrinth publishing requires a Modrinth PAT `MODRINTH_TOKEN` +# Will skip without error if not present. +# CurseForge publishing requires a CurseForge API token `CURSEFORGE_TOKEN` +# Will skip without error if not present. +# Preference using the root gradle.properties file to configure releases. +name: Release + on: - push: - tags: - - 'v*' + workflow_dispatch: -name: Release Mod +permissions: + contents: write jobs: - build: - runs-on: ubuntu-latest + release: + strategy: + matrix: + java: [ + 25 + ] + os: [ + ubuntu-latest + ] + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: 17 - - name: Build - uses: eskatos/gradle-command-action@v1.3.2 + - name: Checkout repository + uses: actions/checkout@v5 with: - gradle-version: wrapper - arguments: build --stacktrace - - name: Release to Github - uses: softprops/action-gh-release@v1 - if: ${{ success() }} + fetch-depth: 0 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v5 + - name: Setup JDK ${{ matrix.java }} + uses: actions/setup-java@v5 with: - files: release/*.jar - fail_on_unmatched_files: true + distribution: zulu + java-version: ${{ matrix.java }} + - name: Make Gradle wrapper executable + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + - name: Build + run: ./gradlew build neoforge:publishGithub neoforge:publishCurseforge neoforge:publishModrinth fabric:publishGithub fabric:publishCurseforge fabric:publishModrinth --stacktrace env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Release to CurseForge - uses: gradle/gradle-build-action@v2 - if: ${{ success() }} + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + - name: Capture build artifacts + if: ${{ runner.os == 'Linux' && matrix.java == '25' }} + uses: actions/upload-artifact@v5 with: - arguments: :fabric:curseforge :forge:curseforge --stacktrace - env: - CURSE_API_KEY: ${{ secrets.CURSE_API_KEY }} + name: artifacts + path: | + **/build/libs/ diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..979f38b --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,22 @@ +# Synchronizes the repo's labels with a centralized `labels.yml` file. +# Requires `GITHUB_TOKEN` to have write permissions; if not, replace it with a custom token. +name: Sync Labels + +on: + push: + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Setup NodeJS + uses: actions/setup-node@v6 + with: + node-version: 22 + - run: curl https://raw.githubusercontent.com/TerminalMC/.github/HEAD/.github/labels.yml -o ./labels.yml + - run: npx github-label-sync -a '${{ secrets.GITHUB_TOKEN }}' -l 'labels.yml' ${{ github.repository }} diff --git a/.gitignore b/.gitignore index 5f81675..f1ecb28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,26 @@ -.gradle -.idea -.vscode -out -build -run -.architectury-transformer -*.iml -release +# Gradle +.gradle/ + +# Artifacts bin/ +build/ +run/ +runs/ +out/ +classes/ +.eclipse/ + +# IDEA +.idea/ +*.iml +*.ipr +*.iws + +# VSCode +.settings/ +.vscode/ +.classpath +.project + +# Other +.env diff --git a/LICENSE b/LICENSE.txt similarity index 96% rename from LICENSE rename to LICENSE.txt index 1d032b9..ee50ab8 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 AppleTheGolden +Copyright (c) 2020-2024 AppleTheGolden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b907d0 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +
+ +Icon + +## Now Playing + +Shows a popup whenever the active music track changes. + +[![Environment](https://img.shields.io/badge/Environment-Client-blue?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+malUqDnYQEclQneyiIo6likWwUNoKrTqYXPoHTRqSFBdHwbXg4M9i1cHFWVcHV0EQ/AFxdnBSdJESv0sKLWI8uLuH97735e47QGhUmGp2RQFVs4xUPCZmc6ti4BU9CKCP1jGJmXoivZiB5/i6h4/vdxGe5V335xhQ8iYDfCJxlOmGRbxBPLtp6Zz3iUOsJCnE58STBl2Q+JHrsstvnIsOCzwzZGRS88QhYrHYwXIHs5KhEs8QhxVVo3wh67LCeYuzWqmx1j35C4N5bSXNdZqjiGMJCSQhQkYNZVRgIUK7RoqJFJ3HPPwjjj9JLplcZTByLKAKFZLjB/+D3701C9NTblIwBnS/2PbHOBDYBZp12/4+tu3mCeB/Bq60tr/aAOY+Sa+3tfARMLgNXFy3NXkPuNwBhp90yZAcyU9TKBSA9zP6phwwdAv0r7l9a53j9AHIUK+Wb4CDQ2CiSNnrHu/u7ezbvzWt/v0ATphymIBZ6aQAAAAGYktHRAAKAAwAGd6C8noAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfoBgcOHRYlcgoRAAABRklEQVR42u2YMUoDQRRAX0axUzCteIZ4hKn0FDmFhalSWKkgnkHt9AQWwhzBNr2tBGNno82ACwm6EZvxvwdTzP8s7P8zu8w8EJHIDABKKfvAFXAIbP/zmt+AR2CSc54NavFPwDDY4s+BUaorPwy4+3eBy1S3fVSOUoBv/jt2UmMv/A6cAHt1TGqsb36JzcYaMM05X3Tm56UUgLOe+SVa2wE3K2LXa+Sbb8BgRWxjjXzzDRj/EBv3fOarY6WUj8Z+glPgtlPcKbDVM998A/6cVM/GUXlN9WIQlYdUDwvzgMW/AMcp5zwDRsA9sAhQ+AK4Aw5yzs8aEZHY6AR1gjpBnaBOsLHrsE6wM9cJohPUCeoE0Qn+Hp0gOkGdoE5QRMKiE9QJ6gR1gjrBxq7DOsHOXCeITlAnqBNEJ/h7dILoBHWCOkERCcsncuextWq5TzoAAAAASUVORK5CYII=)]() +[![Latest Minecraft](https://img.shields.io/modrinth/game-versions/eNF4Bfla?label=Latest%20Minecraft&color=%2300AF5C&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+malUqDnYQEclQneyiIo6likWwUNoKrTqYXPoHTRqSFBdHwbXg4M9i1cHFWVcHV0EQ/AFxdnBSdJESv0sKLWI8uLuH97735e47QGhUmGp2RQFVs4xUPCZmc6ti4BU9CKCP1jGJmXoivZiB5/i6h4/vdxGe5V335xhQ8iYDfCJxlOmGRbxBPLtp6Zz3iUOsJCnE58STBl2Q+JHrsstvnIsOCzwzZGRS88QhYrHYwXIHs5KhEs8QhxVVo3wh67LCeYuzWqmx1j35C4N5bSXNdZqjiGMJCSQhQkYNZVRgIUK7RoqJFJ3HPPwjjj9JLplcZTByLKAKFZLjB/+D3701C9NTblIwBnS/2PbHOBDYBZp12/4+tu3mCeB/Bq60tr/aAOY+Sa+3tfARMLgNXFy3NXkPuNwBhp90yZAcyU9TKBSA9zP6phwwdAv0r7l9a53j9AHIUK+Wb4CDQ2CiSNnrHu/u7ezbvzWt/v0ATphymIBZ6aQAAAAGYktHRAAKAAwAGd6C8noAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfoBgcOGBJfaDpNAAAE40lEQVR42u2bbYhUVRjHf/tiaRFkWVEaZJG2YNq2mYRFf0o/RPatDYk0ssAIKi0zbfMtXKxILRJqowy3FyqjD2ZvlPEQFlLh1qpIRVLWEq7p+rK11tpuH+ZZmqZ7Z3Zm79x5aZ5vc8+9557/f87//5zznBmoxP87qgr1YjMbBpzpHzsl9ZY9AQ56JjAbmAqM8KYeYCvQCrweJxlVMQGvAhqBVcD5GW7fAywGNkrqL3kCzKwBWAtcleWjnwPzJX1WkgSY2TnAMuB2oCbHbvqBN4EHJP1YEgSY2QjgHqAJOCWibn8HngZWSuouWgLM7AbgKWBsniZWB/AQ8FJU/lAVEfDLXOdXxmTeX7g/fFpQApJ0fgdQHXMKj8QfqnIEfhJwd8Q6H6o/NEs6mlcCPJ/fCDwOnFdkq9oO4BHgeUl9kRNgZpNd51OLfHn/pfvD1kgIMLPRwNIC6Xyo/rBQ0g85E2Bmc4HVwMklutn7DbhfUkvYDdVpwA/3fF5D6UYNMNax5CyBc4Fm4JZCbp9ziM3AvZL2RGWCU9wEryhy4NvdBD/JyQTNrAa4E2iRdDylrRqY5TNidBGmwSZfJveljLsWmAs8K+mvTB5wFrAO2GVmM5IbJPVJ2gCMAxYB3UUAvAd4DKiTtCEA/DSfFev4pwKVdgZMBL5OuvSeO+nuEH9YBdxcAH/oB14FFkv6KWBsdZ7Brku6PElSeyYCLgHaUi4fB9YDD0vaX8ybITMbCTwIzAdOSGmul/RVLgQMRJdPt7WS/izAdvjnJJ33B+h8DrASOCPk+f8QkO3KbiTwKLDDzBpTGyW9DVwEzAOORLygWQGMk9QaAH6af2ktacAPOgukmwGpscWn4o6Afs4GlhNNSWyBpL0B7xgPPAHMGGR/Q5ZAUAz4wxJJnQH91bs/XJ0l+G1O7raAPk8DFoboPHYCUv3hSUl/hPjDWuCCDP3sBZaE6HwYcFsGnWdFQJS7uwF/aE/jD3XuD4cDnu92nY9Po/Ptueg8nx6QyR/uS827/o7TfYt9l38Jr3hpa1+IzlcD10cwprxKIMwfWoBlkg4EvGuCz46dISSt8CVsbUTjiZ2AgTjk8gj0hxCdNwOjIh5HXj0gXZyabv0Qks9HxTGwWuKNC4E3zOxDYKakg0lp7TVgetw7qULV+KYDY5I+jykE+EISUDRRIaBCQIWACgEVAioE/DuOlTHeY4Mh4JcyJqAjIwGSDgNHyxD8kaAfUIR5wAdlSMD72ZhgM9BbRuB7HNPgCPA985wyIaHXd57tWaVBSS8DU4D2EgbfDlwuaVNO6wBJbUADibLU/hIC3kXi8HZyagUoNTIWRPyI/Dkz20j4mVuxRNozzJwISCKiC1hkZutJ1OUbiwz8RyQOUnZm81DWJTFJ3wI3mdm1wBpgYoGBf0Pi+P6dWPcCkrYA9cCtQGcBgB8kccgyIVfwOc2AFBL6gFYz2+SmMw84MYa09iLQJOnXoXYWSVVY0iH3hxd8wdGYR53Pk7Qrqg4jLYtL+s794Rr3h0kRdb2bxBH5uyVRD5D0MXCp+8O+IXR1wGV1cT7ARz4DQvzhLWCBryGGZ6HzZ4ClvjvNW+T9ZMj/47M8af0wK8Mjm13n38eRSmI7GvOfuMx2ItYE3NJG4jjd4sylsdcEHWCDG1uyyTXEDb4SlYC/AW0t3IQpiA17AAAAAElFTkSuQmCC)](https://modrinth.com/project/eNF4Bfla/versions) + +[![Loader](https://img.shields.io/badge/Available%20for-Fabric-dbd0b4?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAcBAMAAACNPbLgAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpX5UHMwgIpihOtlFRRxLFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6uKk6CIl/i8ptIj14Lgf7+497t4BQr3MNKsrAmi6bSZjUSmTXZUCr+iHiADG0Cszy4inFtPoOL7u4ePrXZhndT735xhQcxYDfBJxhBmmTbxBPLtpG5z3iUVWlFXic+JJky5I/Mh1xeM3zgWXBZ4pmunkPLFILBXaWGljVjQ14hnikKrplC9kPFY5b3HWylXWvCd/YTCnr6S4TnMUMSwhjgQkKKiihDJshGnVSbGQpP1oB/+I60+QSyFXCYwcC6hAg+z6wf/gd7dWfnrKSwpGge4Xx/kYBwK7QKPmON/HjtM4AfzPwJXe8lfqwNwn6bWWFjoCBreBi+uWpuwBlzvA8JMhm7Ir+WkK+TzwfkbflAWGboG+Na+35j5OH4A0dbV8AxwcAhMFyl7v8O6e9t7+PdPs7wd+dXKrd9SjeQAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cLFAcgIbOcUjoAAAAbUExURQAAAB0tQTg0KoB6bZqSfq6mlLyynMa8pdvQtJRJT6UAAAABdFJOUwBA5thmAAAAAWJLR0QB/wIt3gAAAF5JREFUGNN10FENwCAMhOFqOQuzMAtYOAtYqGw6mkEvhL59yR9Ca5YDqyOC465eKYqQm6LoCkVwnwQOBYKdeA5l51zhFtrsnPmg6m3Z2akk15dFH1lWFQVxlUFv+2sAJlA9O7NwQRQAAAAASUVORK5CYII=)](https://fabricmc.net/) +[![Loader](https://img.shields.io/badge/Available%20for-Quilt-9115ff?logo=data:image/png;base64,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)](https://quiltmc.org/) +[![Loader](https://img.shields.io/badge/Available%20for-NeoForge-f16436?logo=data:image/png;base64,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)](https://neoforged.net/) + +[![Available on Modrinth](https://img.shields.io/modrinth/dt/eNF4Bfla?label=Available%20on%20Modrinth&logo=modrinth&logoColor=%2300AF5C)](https://modrinth.com/project/eNF4Bfla) +[![Available on GitHub](https://img.shields.io/github/downloads/Scotsguy/now-playing/total?label=Available%20on%20GitHub&logo=github&logoColor=white)](https://github.com/Scotsguy/now-playing) + +
+ +### About + +Have you ever wondered what all the songs in Minecraft are actually called? Sure, you could check +the soundtrack to find out, but then you have to listen to every song, hoping it's the one you're +thinking of. + +With this mod, wonder no more! A toast will pop up in the top right corner of your screen or just +above your hotbar (configurable), telling you what song you're about to listen to. + +### Setup + +
+General Options + +- Only use key or command + - Whether to only show pop-up when the keybind is pressed or the `/nowplaying` command is used. +- Music pop-up style + - How to display pop-up for background music. Choice of toast, hotbar (status bar) message, or + nothing. +- Jukebox pop-up style + - How to display pop-up for jukebox music. Choice of toast, hotbar (status bar) message, or + nothing. +- Fallback to toast + - Whether to display a toast for music set to hotbar, if not possible to show a hotbar message. +- Silent toast + - Whether the toast should make a whoosh noise. +- Toast scale + - The size of the toast. +- Simple toast + - Whether to show the "Now Playing" text as well as the track title in the toast. +- Dark toast + - Whether to use a dark background for the toast. +- Toast display time + - How long the toast will be displayed for. +- Hotbar display time + - How long the hotbar message will be displayed for. +- Narrate pop-up + - Whether pop-ups should be narrated, if the narrator is enabled. + +
+ +
+Custom Sprites + +Now Playing supports changing which disc sprites are displayed for each background music track +via a resource pack. First, create a `nowplaying` folder in the `assets` folder of your pack, +and place a `sprites.json` file in that folder, as shown below. + +``` +assets +├── minecraft +└── nowplaying + └── sprites.json +``` + +Next, populate the `sprites.json` file with key-value pairs, where the key is the music resource +location (or part thereof), and the value is the sprite location, as shown below. + +```json +{ + "minecraft:music/game": "minecraft:textures/item/music_disc_cat", + "minecraft:music/game/creative": "minecraft:textures/item/music_disc_blocks", + "minecraft:music/game/creative/taswell": "minecraft:textures/item/music_disc_chirp", + "minecraft:music/game/nether": "minecraft:textures/item/music_disc_pigstep" +} +``` + +If you only specify part of a music resource location, the corresponding disc will be shown for +all music tracks in that location, except those that have a more specific definition. In the +example above: + +- All tracks in the `game` folder will use the `cat` sprite, except; + - tracks in the `creative` folder, which will use the `blocks` sprite, except; + - the `taswell` track , which will use the `chirp` sprite + - tracks in the `nether` folder, which will use the `pigstep` sprite. + +You can use any existing music disc sprite, or any sprite provided by a resourcepack. + +
+ +
+Custom Music Track Titles + +If you have a resource pack that adds custom background music, you can specify titles using a +translation file (e.g. `en_us.json`). First, create a `nowplaying` folder in the `assets` folder +of your pack, create a `lang` folder inside, and place a translation file in that folder, as shown +below. + +``` +assets +├── minecraft +└── nowplaying + └── lang + └── en_us.json +``` + +Next, populate the translation file with key-value pairs, where the key is the music resource +location, prefixed with `nowplaying`, and the value is the translated title of the track, as +shown below. + +```json +{ + "nowplaying.minecraft:music/game/a_familiar_room": "Aaron Cherof - A Familiar Room", + "nowplaying.minecraft:music/game/an_ordinary_day": "Kumi Tanioka - An Ordinary Day", + "nowplaying.minecraft:music/game/ancestry": "Lena Raine - Ancestry", + "nowplaying.minecraft:music/game/clark": "C418 - Clark", + "nowplaying.minecraft:music/game/creative/aria_math": "C418 - Aria Math" +} +``` + +Old translation files which use the `music.nowplaying.[name]` format are still supported, but +have a lower priority than the new format. + +
+ +### Contact + +[![GitHub Issues](https://img.shields.io/github/issues/Scotsguy/now-playing?logo=github&label=Issues)](https://github.com/Scotsguy/now-playing/issues) + +[![License](https://img.shields.io/github/license/scotsguy/now-playing?label=License&logo=github&logoColor=white)](https://github.com/scotsguy/now-playing/blob/HEAD/LICENSE.txt) diff --git a/build.gradle b/build.gradle index 012875e..a7a06e6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,58 +1,228 @@ -plugins { - alias(libs.plugins.architectury) - alias(libs.plugins.loom) apply false - alias(libs.plugins.vineflower) apply false - alias(libs.plugins.cursegradle) apply false -} +import me.modmuss50.mpp.ReleaseType +import util.EnvUtil +import util.PropUtil +import util.StaticUtil + +import java.time.LocalDate -architectury { - minecraft = libs.versions.minecraft.get() +plugins { + id("net.fabricmc.fabric-loom") version("${loom_version}") apply(false) + id("net.neoforged.moddev") version("${moddev_version}") apply(false) + id("net.neoforged.licenser") version("${licenser_version}") apply(false) + id("me.modmuss50.mod-publish-plugin") version("${mpp_version}") + id("org.ajoberstar.grgit.service") version("${grgitservice_version}") } subprojects { - apply plugin: "dev.architectury.loom" - apply plugin: 'io.github.juuxel.loom-vineflower' - - dependencies { - minecraft libs.minecraft - if (libs.versions.useParchment.get() == "1") { - mappings loom.layered() { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-${libs.versions.minecraft.get()}:${libs.versions.parchment.get()}@zip") + final env = new EnvUtil(providers) + final prop = new PropUtil(project) + final cLog = StaticUtil.versionChangelog(rootProject.file("changelog.md"), mod_version) + + // Append Minecraft version extension + mod_version = "${mod_version}+${minecraft_version}" + // Append dependency version extension, if configured + if (prop.has("dep_ext_${name}")) { + final data = prop.get("dep_ext_${name}").split(":") + final dep_abbrev = data[0] + // Strip meta from dependency version + final dep_ver = prop.get(data[1]).split("\\+")[0] + mod_version = "${mod_version}+${dep_abbrev}${dep_ver}" + } + version = mod_version + group = mod_group + + // Configure license headers + apply(plugin: "net.neoforged.licenser") + final licenseDir = "src/main/resources/assets/${mod_id}/license/" + license { + include("**/*.java") // Java files only + // Apply main license header + header = rootProject.project("common").file("${licenseDir}HEADER.txt") + properties { + project_name = mod_name + owner_name = mod_owner + year = LocalDate.now().getYear().toString() + } + // Apply attribution license headers, if configured + if (att_license_mods != "") { + att_license_mods.split(",").each { String modId -> + //noinspection GroovyAssignabilityCheck + matching({ include(prop.list("att_license_files_${modId}")) }) { + header = rootProject.project("common").file("${licenseDir}${modId}/HEADER.txt") + it + } } - } else { - mappings loom.officialMojangMappings() } } - loom { - silentMojangMappingsLicense() + // Configure multi-site publishing + if (name != "common") { + apply(plugin: "me.modmuss50.mod-publish-plugin") + apply(plugin: "org.ajoberstar.grgit.service") + + afterEvaluate { sp -> + publishMods { + // Common configuration + file = jar.archiveFile + version = mod_version + type = ReleaseType.of(mod_version_type) + displayName = "v${mod_version}-${StaticUtil.capsLoader(sp.name)}" + modLoaders.addAll(prop.safeList("mod_loaders_${sp.name}")) + maxRetries = 5 + dryRun = !env.has("GITHUB_TOKEN") + && !env.has("MODRINTH_TOKEN") + && !env.has("CURSEFORGE_TOKEN") + // GitHub configuration + github { + parent project(":").tasks.named("publishGithub") + accessToken = env.safe("GITHUB_TOKEN") + if (build_sources_jar == "true") additionalFiles.from(sourcesJar.archiveFile) + if (build_javadoc_jar == "true") additionalFiles.from(javadocJar.archiveFile) + return void + } + // Modrinth configuration + modrinth { + accessToken = env.safe("MODRINTH_TOKEN") + projectId = prop.safe("mod_modrinth_id") + minecraftVersions.addAll(prop.safeList("mc_versions_${sp.name}")) + changelog = cLog + prop.safeList("${sp.name}_deps").each { dep -> + try { + final depData = prop.list("d_${sp.name}_${dep}") + if (depData.length > 2 && depData[2] != "-") { + final mrDepData = depData[2].split(":") + final type = mrDepData[0] + if (type == "req") { + requires { id = mrDepData[1] } + } else if (type == "opt") { + optional { id = mrDepData[1] } + } else if (type == "inc") { + incompatible { id = mrDepData[1] } + } else if (type == "emb") { + embeds { id = mrDepData[1] } + } else { + throw new IllegalArgumentException( + "Unrecognized dependency type: ${mrDepData[0]}" + ) + } + } + } catch (Exception ex) { + logger.error("Error processing Modrinth dependency '${dep}' for " + + "subproject '${sp.name}'. Check dependency property format.") + throw ex + } + } + // Sync Modrinth description with README + if (prop.has("mod_github_repo")) { + projectDescription = rootProject.file("README.md").text.replaceAll( + "src=\"\\./", + "src=\"https://raw.githubusercontent.com/${prop.get("mod_github_repo")}/HEAD/" + ) + return + } + } + // CurseForge configuration + curseforge { + accessToken = env.safe("CURSEFORGE_TOKEN") + projectId = prop.safe("mod_curseforge_id") + projectSlug = prop.safe("mod_curseforge_slug") + minecraftVersions.addAll(prop.safeList("mc_versions_${sp.name}").collect { ver -> + final hyphenIdx = ver.indexOf("-") + if (hyphenIdx != -1) { + return ver.substring(0, hyphenIdx) + "-snapshot" + } else { + return ver + } + }) + changelog = cLog + prop.safeList("${sp.name}_deps").each { dep -> + try { + final depData = prop.list("d_${sp.name}_${dep}") + if (depData.length > 3 && depData[3] != "-") { + final cfDepData = depData[3].split(":") + final type = cfDepData[0] + if (type == "req") { + requires(cfDepData[1]) + } else if (type == "opt") { + optional(cfDepData[1]) + } else if (type == "inc") { + incompatible(cfDepData[1]) + } else if (type == "emb") { + embeds(cfDepData[1]) + } else { + throw new IllegalArgumentException( + "Unrecognized dependency type: ${cfDepData[0]}" + ) + } + } + } catch (Exception ex) { + logger.error("Error processing CurseForge dependency '${dep}' for " + + "subproject '${sp.name}'. Check dependency property format.") + throw ex + } + } + return void + } + } + final hasSpConf = prop.has("mod_loaders_${name}") && prop.has("mc_versions_${name}") + tasks.named("publishGithub") { + onlyIf { + return hasSpConf + && prop.has("mod_github_repo") + && (it["dryRun"].get() || env.has("GITHUB_TOKEN")) + } + } + tasks.named("publishModrinth") { + onlyIf { + return hasSpConf + && prop.has("mod_modrinth_id") + && (it["dryRun"].get() || env.has("MODRINTH_TOKEN")) + } + } + tasks.named("publishCurseforge") { + onlyIf { + return hasSpConf + && prop.has("mod_curseforge_id") + && prop.has("mod_curseforge_slug") + && (it["dryRun"].get() || env.has("CURSEFORGE_TOKEN")) + } + } + } } } -allprojects { - apply plugin: "java" - apply plugin: "architectury-plugin" - apply plugin: "maven-publish" - apply plugin: "com.matthewprenger.cursegradle" - - archivesBaseName = rootProject.archives_base_name - version = rootProject.mod_version - group = rootProject.maven_group - - repositories { - maven { url "https://maven.shedaniel.me/" } - maven { url "https://maven.terraformersmc.com" } - maven { url "https://maven.parchmentmc.org" } - maven { url "https://maven.quiltmc.org/repository/release/" } +/* + This configuration, combined with the subproject configuration above, allows + multiple files to be uploaded to a single GitHub release. + */ +publishMods { + final env = new EnvUtil(providers) + final prop = new PropUtil(project) + version = "${mod_version}+${minecraft_version}" + displayName = "v${mod_version}+${minecraft_version}" + type = ReleaseType.of(mod_version_type) + dryRun = !env.has("GITHUB_TOKEN") + github { + accessToken = env.safe("GITHUB_TOKEN") + repository = prop.safe("mod_github_repo") + commitish = grgitService.service.get().grgit.branch.current().name + tagName = "v${mod_version}+${minecraft_version}" + allowEmptyFiles = true + // Link to header in changelog file + changelog = "[Changelog](./changelog.md#${mod_version.replaceAll("\\.", "")})" } - - tasks.withType(JavaCompile).configureEach { - options.encoding = "UTF-8" - options.release = 17 + tasks.named("publishGithub") { + onlyIf { + return prop.has("mod_github_repo") + && (it["dryRun"].get() || env.has("GITHUB_TOKEN")) + } } +} - java { - withSourcesJar() +file(".").eachFile { + if (it.isFile() && it.name.endsWith(".gradle") + && it.name != "build.gradle" + && it.name != "settings.gradle") { + apply(from: it.name) } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..fbd5c69 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,3 @@ +plugins { + id("groovy-gradle-plugin") +} diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle new file mode 100644 index 0000000..db6cb7e --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -0,0 +1,265 @@ +plugins { + id("java-library") + id("maven-publish") +} + +// Configure project dependency repos, except for mod loader repos provided by +// their respective plugins +repositories { + mavenCentral() + mavenLocal() + exclusiveContent { + forRepository { + //noinspection ForeignDelegate + maven { + name = "ParchmentMC" + url = uri("https://maven.parchmentmc.org") + } + } + filter { + includeGroup("org.parchmentmc.data") + } + } + exclusiveContent { + forRepository { + //noinspection ForeignDelegate + maven { + name = "Fabric" + url = uri("https://maven.fabricmc.net") + } + } + filter { + includeGroupAndSubgroups("net.fabricmc") + } + } + maven { + name = "Modrinth" + url = uri("https://api.modrinth.com/maven") + } + maven { + name = "Shedaniel" + url = uri("https://maven.shedaniel.me") + } + maven { + name = "isXander" + url = uri("https://maven.isxander.dev/releases") + } +} + +// Set output file name +base { + archivesName.set("${mod_id}-${project.name}") +} + +// Configure Java build +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(java_version)) + if (build_sources_jar == "true") withSourcesJar() + if (build_javadoc_jar == "true") withJavadocJar() +} + +// Declare capabilities on the outgoing configurations +final capabilityVariants = ["apiElements", "runtimeElements"] +if (build_sources_jar == "true") capabilityVariants.add("sourcesElements") +if (build_javadoc_jar == "true") capabilityVariants.add("javadocElements") +capabilityVariants.each { variant -> + configurations.named(variant).configure { + outgoing { + capability("$group:${base.archivesName.get()}:$version") + capability("$group:$mod_id:$version") + } + } + publishing.publications.configureEach { + suppressPomMetadataWarningsFor(variant) + } +} + +// Configure main file +jar { + from(rootProject.file("LICENSE.txt")) { + rename { "LICENSE_${mod_id}.txt" } + } + + manifest { + attributes([ + "Specification-Title" : mod_name, + "Specification-Vendor" : mod_owner, + "Specification-Version" : project.jar.archiveVersion, + "Implementation-Title" : project.name, + "Implementation-Version" : project.jar.archiveVersion, + "Implementation-Vendor" : mod_owner, + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + "Timestamp" : System.currentTimeMillis(), + "Built-On-Java" : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + "Built-On-Minecraft" : minecraft_version + ]) + } +} + +// Configure sources file +if (build_sources_jar == "true") { + sourcesJar { + from(rootProject.file("LICENSE.txt")) { + rename { "LICENSE_${mod_id}.txt" } + } + } +} + +// Process Gradle property expansion for metadata files +processResources { + final expandProps = [ + // Mod + "mod_version" : mod_version, + "mod_group" : mod_group, + "mod_id" : mod_id, + "mod_name" : mod_name, + "mod_description_fabric" : mod_description.replace("\n", "\\n"), + "mod_description_neoforge" : mod_description, + "mod_icon" : "assets/${mod_id}/icon.png", + "mod_owner" : mod_owner, + "mod_authors_list" : asJsonList(mod_authors), + "mod_authors_string" : mod_authors.split(",").join(", "), + "mod_contributors_list" : asJsonList("${mod_contributors},${mod_translators},${mod_credits}"), + "mod_contributors_string" : asTomlList("${mod_contributors},${mod_translators},${mod_credits}"), + "mod_license" : mod_license, + // Links + "homepage_url" : homepage_url, + "sources_url" : sources_url, + "issues_url" : issues_url, + "contact_url" : contact_url, + // Java + "java_version" : java_version, + "java_versions_fabric_list" : asJsonList(java_versions_fabric), + "java_versions_neoforge" : java_versions_neoforge, + // Minecraft + "minecraft_versions_fabric_list": asJsonList(minecraft_versions_fabric), + "minecraft_versions_neoforge": minecraft_versions_neoforge, + // Mixin + "mixin_version_min" : mixin_version_min, + "mixinextras_version_min" : mixinextras_version_min, + // Fabric + "fabric_loader_versions_list": asJsonList(fabric_loader_versions), + "fabric_api_versions_list" : asJsonList(fabric_api_versions), + "fabric_entrypoints_main" : asJsonListPrefixed(fabric_entrypoints_main, "${mod_group}.${mod_id}."), + "fabric_entrypoints_client" : asJsonListPrefixed(fabric_entrypoints_client, "${mod_group}.${mod_id}."), + "fabric_entrypoints_server" : asJsonListPrefixed(fabric_entrypoints_server, "${mod_group}.${mod_id}."), + "fabric_entrypoints_modmenu" : asJsonListPrefixed(fabric_entrypoints_modmenu, "${mod_group}.${mod_id}."), + // NeoForge + "neoforge_versions" : neoforge_versions, + // Dependencies: + "fabric_depends" : "", + "fabric_recommends" : "", + "fabric_suggests" : "", + "fabric_conflicts" : "", + "fabric_breaks" : "", + "neoforge_all_deps" : "" + ] + + // Apply property-defined dependencies + safePropList("fabric_deps").each { dep -> + try { + final depData = propList("d_fabric_${dep}") + if (depData.length > 1 && depData[1] != "-") { + final loaderData = depData[1].split(":") + final key = "fabric_${loaderData[0]}".toString() + final prefix = expandProps[key] == "" ? "" : ",\n " + final depList = "[${asJsonList(project.property("vr_fabric_${dep}").toString())}]" + final value = "${expandProps[key]}${prefix}\"${loaderData[1]}\": ${depList}" + expandProps[key] = value.toString() +// logger.info("Fabric metadata '${key}': ${expandProps[key]}") + } + } catch (Exception ex) { + logger.error("Error processing Fabric dependency metadata for '${dep}'. " + + "Check dependency property format.") + throw ex + } + } + if (!expandProps["fabric_depends"].isBlank()) { + expandProps["fabric_depends"] = "${expandProps["fabric_depends"]},".toString() + } + safePropList("neoforge_deps").each { dep -> + try { + final depData = propList("d_neoforge_${dep}") + if (depData.length > 1 && depData[1] != "-") { + final loaderData = depData[1].split(":") + expandProps["neoforge_all_deps"] = expandProps["neoforge_all_deps"] + + "[[dependencies.${mod_id}]]\n" + + "modId=\"${loaderData[1]}\"\n" + + "type=\"${loaderData[0]}\"\n" + + "versionRange=\"${project.property("vr_neoforge_${dep}")}\"\n" + + "side=\"CLIENT\"\n\n" + } + } catch (Exception ex) { + logger.error("Error processing NeoForge dependency metadata for '${dep}'. " + + "Check dependency property format.") + throw ex + } + } +// logger.info("NeoForge metadata 'neoforge_all_deps': ${expandProps["neoforge_all_deps"]}") + + filesMatching(["pack.mcmeta", "*.mod.json", "*.mixins.json", "META-INF/*.toml"]) { + expand(expandProps) + } + inputs.properties(expandProps) +} + +// Configure publication +publishing { + publications { + mavenJava(MavenPublication) { + artifactId = base.archivesName.get() + from(components.java) + } + } +} + +/** + * Converts a CSV list into an FMJ-usable format. + */ +static asJsonList(String csvString) { + return csvString.split(",") + .findAll { !it.isBlank() } + .collect { "\"${it.strip()}\"" } + .join(",") +} + +/** + * Converts a CSV list into an FMJ-usable list format, prefixing each element with the source + * package (${mod_group}.${mod_id}). + */ +static asJsonListPrefixed(String csvString, String prefix) { + return csvString.split(",") + .findAll { !it.isBlank() } + .collect { "\"${prefix}${it.strip()}\"" } + .join(",") +} + +/** + * Converts a CSV list into an NMT-usable format. + */ +static asTomlList(String csvString) { + return csvString.split(",") + .findAll { !it.isBlank() } + .collect { it.strip() } + .join(", ") +} + +/** + @return the value of the property, CSV-split. + */ +String[] propList(String propertyName) { + final list = project.property(propertyName).toString().split(",") + for (int i = 0; i < list.length; i++) { + list[i] = list[i].strip() + } + return list +} + +/** + @return the value of the property CSV-split if it is set, an empty array otherwise. + */ +String[] safePropList(String propertyName) { + return project.hasProperty(propertyName) && !project.property(propertyName).toString().isBlank() + ? propList(propertyName) + : [] +} diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle new file mode 100644 index 0000000..44e0770 --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-loader.gradle @@ -0,0 +1,48 @@ +plugins { + id("multiloader-common") +} + +// Access common subproject tasks +configurations { + commonJava { + canBeResolved = true + } + commonResources { + canBeResolved = true + } +} + +// Add common subproject dependency +dependencies { + compileOnly(project(":common")) { + capabilities { + requireCapability("$group:$mod_id") + } + } + commonJava project(path: ":common", configuration: "commonJava") + commonResources project(path: ":common", configuration: "commonResources") +} + +// Add common subproject task dependencies +compileJava { + dependsOn(configurations.commonJava) + source(configurations.commonJava) +} +processResources { + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} +if (build_sources_jar == "true") { + tasks.named("sourcesJar", Jar) { + dependsOn(configurations.commonJava) + from(configurations.commonJava) + dependsOn(configurations.commonResources) + from(configurations.commonResources) + } +} +if (build_javadoc_jar == "true") { + tasks.named("javadoc", Javadoc) { + dependsOn(configurations.commonJava) + source(configurations.commonJava) + } +} diff --git a/buildSrc/src/main/groovy/util/EnvUtil.groovy b/buildSrc/src/main/groovy/util/EnvUtil.groovy new file mode 100644 index 0000000..b7ecef2 --- /dev/null +++ b/buildSrc/src/main/groovy/util/EnvUtil.groovy @@ -0,0 +1,25 @@ +package util + +import org.gradle.api.provider.ProviderFactory + +class EnvUtil { + final ProviderFactory providers + + EnvUtil(ProviderFactory providers) { + this.providers = providers + } + + /** + @return {@code true} if the environment variable is set. + */ + boolean has(String variableName) { + return !safe(variableName).isBlank() + } + + /** + @return the value of the environment variable if it is set, an empty string otherwise. + */ + String safe(String variableName) { + return providers.environmentVariable(variableName).getOrElse("") + } +} diff --git a/buildSrc/src/main/groovy/util/PropUtil.groovy b/buildSrc/src/main/groovy/util/PropUtil.groovy new file mode 100644 index 0000000..ed4d640 --- /dev/null +++ b/buildSrc/src/main/groovy/util/PropUtil.groovy @@ -0,0 +1,84 @@ +package util + +import org.gradle.api.Project +import org.gradle.api.artifacts.ExternalModuleDependency + +import java.util.function.BiFunction + +class PropUtil { + final Project project + + PropUtil(Project project) { + this.project = project + } + + /** + @return {@code true} if the property is set. + */ + boolean has(String propertyName) { + return project.hasProperty(propertyName) && !project.property(propertyName).toString().isBlank() + } + + /** + @return the value of the property. + */ + String get(String propertyName) { + return project.property(propertyName) + } + + /** + @return the value of the property if it exists, an empty string otherwise. + */ + String safe(String propertyName) { + return has(propertyName) ? project.property(propertyName) : "" + } + + /** + @return the value of the property, CSV-split. + */ + String[] list(String propertyName) { + return project.property(propertyName).toString().split(",") + .findAll { !it.isBlank() } + .collect { it.strip() } + } + + /** + @return the value of the property CSV-split if it is set, an empty array otherwise. + */ + String[] safeList(String propertyName) { + return has(propertyName) ? list(propertyName) : [] + } + + /** + * Applies configured dependencies for a subproject. + * @param pName the subproject name. + * @param selector the dependency configuration selector. + */ + void applyDependencies( + String pName, + BiFunction> selector + ) { + safeList("${pName}_deps").each { dep -> + try { + final depData = list("d_${pName}_${dep}") + if (depData[0] != "-") { + final mavenData = depData[0].split(":") + final mavenGroup = mavenData[3] + final mavenProject = mavenData[4] + final subVersion = ((mavenData.length > 6 && mavenData[6] != "-") + ? project.property(mavenData[6]) + : project.property("v_${dep}")).toString() + final mavenVersion = "${mavenData[5]}".replace("\$v", subVersion) + def gradleDep = "${mavenGroup}:${mavenProject}:${mavenVersion}" + for (int i = 2; i >= 0; i--) { + gradleDep = selector.apply(mavenData[i], gradleDep) + } + } + } catch (Exception ex) { + logger.error("Error processing Gradle dependency '${dep}' for subproject " + + "'${pName}'. Check dependency property format.") + throw ex + } + } + } +} diff --git a/buildSrc/src/main/groovy/util/StaticUtil.groovy b/buildSrc/src/main/groovy/util/StaticUtil.groovy new file mode 100644 index 0000000..d3e040c --- /dev/null +++ b/buildSrc/src/main/groovy/util/StaticUtil.groovy @@ -0,0 +1,46 @@ +package util + +class StaticUtil { + /** + * Converts a lowercase mod loader name into its formal version. + */ + static String capsLoader(String loader) { + switch (loader) { + case "fabric": return "Fabric" + case "quilt": return "Quilt" + case "forge": return "Forge" + case "neoforge": return "NeoForge" + default: return loader + } + } + + /** + @returns the latest changelog from the file, verified to match the version. + */ + static String versionChangelog(File file, String version) { + final lines = file.readLines() + final builder = new StringBuilder() + // Changelog version header is on the third line of the file; check it + if (version != lines.get(2).substring(3)) { + throw new IllegalArgumentException(String.format( + "Mod version '%s' does not match changelog version '%s'", + version, + lines.get(2).substring(3) + )) + } else { + // Iterate over content lines + for (int i = 4; i < lines.size(); i++) { + final line = lines.get(i) + if (line.startsWith("## ")) { + // Encountered next changelog header; finish + break + } else { + // Append the content line, respecting blank lines + if (!builder.isEmpty()) builder.append("\n") + if (!line.isBlank()) builder.append(line) + } + } + } + return builder.toString() + } +} diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..c12712e --- /dev/null +++ b/changelog.md @@ -0,0 +1,51 @@ +# Changelog + +## 2.1.0 + +- Updated to mc26.1.1 + +## 2.0.0 + +- Updated to mc26.1 +- Mod versioning scheme is now `major.mc.minor`: + - `major` is incremented on 'significant' feature changes, or breaking API changes (if + applicable). + - `mc` is never reset, and is incremented on every MC release, irrespective of whether a mod + update was required. + - `minor` is reset when `major` is changed, and is incremented on every update that does not + change either of the previous two numbers. + +## 1.6.0 + +- Changed mod ID to `nowplaying` on all platforms + +## 1.5.16 + +- Fixed toast queueing when using the Dynamic FPS mod +- Fixed Modrinth links + +## 1.5.15 + +- Fixed 'Silent Toast' option not working on 1.21.5 + +## 1.5.14 + +- Fixed partial paths in sprites.json being overwritten by defaults + +## 1.5.13 + +- Updated Russian translation (rfin0) + +## 1.5.13-beta.1 + +- Added support for defining custom disc sprite locations +- Added option for dark mode toast +- Added a keybind to play the next background music track + +## 1.5.12 + +- Added Spanish translations (warbacon) + +## 1.5.11 + +- Added Russian translation (rfin0) diff --git a/common/build.gradle b/common/build.gradle index 970015e..bb6ab6d 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,25 +1,86 @@ -dependencies { - // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies - // Do NOT use other classes from fabric loader - modImplementation libs.fabric.loader +import util.PropUtil - modImplementation libs.cloth.common +plugins { + id("multiloader-common") + id("net.fabricmc.fabric-loom") } -architectury { - common(rootProject.enabled_platforms.split(",")) +// Configure common dependencies +dependencies { + // Minecraft + minecraft("com.mojang:minecraft:${minecraft_version}") + + // ASM + compileOnly("org.ow2.asm:asm:${asm_version}") + compileOnly("org.ow2.asm:asm-analysis:${asm_version}") + compileOnly("org.ow2.asm:asm-commons:${asm_version}") + compileOnly("org.ow2.asm:asm-tree:${asm_version}") + compileOnly("org.ow2.asm:asm-util:${asm_version}") + + // Mixin + compileOnly("net.fabricmc:sponge-mixin:${mixin_version}") + + // MixinExtras + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${mixinextras_version}")) + + // Apply property-defined dependencies + final selector = (String type, dep) -> { + switch (type) { + case "anp": //noinspection DependencyNotationArgument + return annotationProcessor(dep) + break + case "api": //noinspection DependencyNotationArgument + return api(dep) + break + case "con": //noinspection DependencyNotationArgument + return compileOnly(dep) + break + case "imp": //noinspection DependencyNotationArgument + return implementation(dep) + break + case "-": + return dep + break + default: + throw new IllegalArgumentException("Unrecognized dependency type: ${type}") + } + } + new PropUtil(project).applyDependencies(project.name, selector) } -publishing { - publications { - mavenCommon(MavenPublication) { - artifactId = rootProject.archives_base_name - from components.java +// Configure Loom +loom { + // Apply common classTweaker if it exists + def aw = file("src/main/resources/${mod_id}.classtweaker") + if (aw.exists()) accessWidenerPath.set(aw) + if (aw.exists()) { + validateAccessWidener { accessWidener = aw } + afterEvaluate { + validateAccessWidener.run() } } +} - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. +// Set up access to common files +configurations { + commonJava { + canBeResolved = false + canBeConsumed = true + } + commonResources { + canBeResolved = false + canBeConsumed = true } + configureEach { + resolutionStrategy.eachDependency { details -> + if (details.requested.group == "org.ow2.asm") { + details.useVersion(asm_version) + details.because("Mixin config plugin requires new ASM") + } + } + } +} +artifacts { + commonJava sourceSets.main.java.sourceDirectories.singleFile + commonResources sourceSets.main.resources.sourceDirectories.singleFile } diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/LevelRendererMixinImpl.java b/common/src/main/java/com/github/scotsguy/nowplaying/LevelRendererMixinImpl.java deleted file mode 100644 index 0707e91..0000000 --- a/common/src/main/java/com/github/scotsguy/nowplaying/LevelRendererMixinImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.scotsguy.nowplaying; - -import com.github.scotsguy.nowplaying.NowPlayingConfig; -import me.shedaniel.autoconfig.AutoConfig; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.renderer.LevelRenderer; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -public class LevelRendererMixinImpl { - public static void modifyRecordPlayingOverlay(Gui gui, Component text) { - NowPlayingConfig config = AutoConfig.getConfigHolder(NowPlayingConfig.class).getConfig(); - if (config.jukeboxStyle == NowPlayingConfig.Style.Hotbar) { - gui.setNowPlaying(text); - } - } -} diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlaying.java b/common/src/main/java/com/github/scotsguy/nowplaying/NowPlaying.java index 893f7d4..146349f 100644 --- a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlaying.java +++ b/common/src/main/java/com/github/scotsguy/nowplaying/NowPlaying.java @@ -1,13 +1,150 @@ +/* + * Copyright (c) 2022-2026 AppleTheGolden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + package com.github.scotsguy.nowplaying; -import me.shedaniel.autoconfig.AutoConfig; -import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; +import com.github.scotsguy.nowplaying.config.Config; +import com.github.scotsguy.nowplaying.gui.toast.NowPlayingToast; +import com.github.scotsguy.nowplaying.mixin.accessor.GuiAccessor; +import com.github.scotsguy.nowplaying.mixin.accessor.MinecraftAccessor; +import com.github.scotsguy.nowplaying.mixin.accessor.ToastManagerAccessor; +import com.github.scotsguy.nowplaying.util.Localization; +import com.github.scotsguy.nowplaying.util.ModLogger; +import com.github.scotsguy.nowplaying.util.SpriteProvider; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; + +import java.util.function.Supplier; + +import static com.github.scotsguy.nowplaying.config.Config.options; +import static com.github.scotsguy.nowplaying.util.Localization.localized; +import static com.github.scotsguy.nowplaying.util.Localization.translationKey; public class NowPlaying { + public static final String MOD_ID = "nowplaying"; + public static final String MOD_NAME = "Now Playing"; + public static final ModLogger LOG = new ModLogger(MOD_NAME); + public static final KeyMapping.Category KEY_CATEGORY = KeyMapping.Category.register( + Identifier.fromNamespaceAndPath(MOD_ID, "group") + ); + public static final KeyMapping DISPLAY_KEY = new KeyMapping( + translationKey("key", "group.display"), InputConstants.Type.KEYSYM, + InputConstants.UNKNOWN.getValue(), KEY_CATEGORY); + public static final KeyMapping NEXT_KEY = new KeyMapping( + translationKey("key", "group.next"), InputConstants.Type.KEYSYM, + InputConstants.UNKNOWN.getValue(), KEY_CATEGORY); + + public static Identifier lastMusic; + public static void init() { - AutoConfig.register(NowPlayingConfig.class, JanksonConfigSerializer::new); + Config.getAndSave(); + } + + public static void onEndTick(Minecraft mc) { + while (DISPLAY_KEY.consumeClick()) { + displayLastMusic(); + } + while (NEXT_KEY.consumeClick()) { + ((MinecraftAccessor)mc).nowplaying$getMusicManager().stopPlaying(); + ((MinecraftAccessor)mc).nowplaying$getMusicManager().startPlaying(mc.getSituationalMusic()); + } + } + + public static void onResourceReload() { + SpriteProvider.onResourceReload(); + } + + public static void displayLastMusic() { + if (lastMusic != null) { + displayMusic(lastMusic); + } else { + Minecraft.getInstance().gui.setOverlayMessage( + localized("message", "notFound").withStyle(ChatFormatting.RED), true); + } + } + + public static void displayMusic(Identifier location) { + Component title = getTranslatedTitle(location.toString()); + display(title, () -> SpriteProvider.getMusicSprite(location, title.getString()), + options().musicStyle); + } + + public static void displayDisc(Component text, Identifier location) { + display(text, () -> SpriteProvider.getDiscSprite(location), options().jukeboxStyle); + } + + private static void display(Component name, Supplier spriteSupplier, + Config.Options.Style style) { + Minecraft mc = Minecraft.getInstance(); + Component message = Component.translatable("record.nowPlaying", name); + + switch(style) { + case Toast -> { + ((ToastManagerAccessor)mc.getToastManager()).nowplaying$getQueued() + .removeIf((toast) -> toast instanceof NowPlayingToast); + mc.getToastManager().addToast(new NowPlayingToast(name, spriteSupplier.get(), + options().toastTime * 1000L, options().toastScale, options().darkToast)); + if (options().narrate) mc.getNarrator().saySystemNow(message); + } + case Hotbar -> { + if (isHotbarVisible(mc.screen)) { + mc.gui.setOverlayMessage(message, true); + ((GuiAccessor)mc.gui).nowplaying$setOverlayMessageTime(options().hotbarTime * 20); + } else if (options().fallbackToast) { + ((ToastManagerAccessor)mc.getToastManager()).nowplaying$getQueued() + .removeIf((toast) -> toast instanceof NowPlayingToast); + mc.getToastManager().addToast(new NowPlayingToast(name, spriteSupplier.get(), + options().toastTime * 1000L, options().toastScale, options().darkToast)); + } + if (options().narrate) mc.getNarrator().saySystemNow(message); + } + } + } + + public static boolean isHotbarVisible(Screen screen) { + return (screen == null || screen instanceof ChatScreen); + } + + private static Component getTranslatedTitle(String location) { + String key = Localization.translationKey(location); + if (!I18n.exists(key)) { + String[] splitLocation = location.split("/"); + if (splitLocation.length > 0) { + String name = splitLocation[splitLocation.length -1]; + if (name != null && !name.isBlank()) { + String oldKey = Localization.translationKey("music", name); + if (I18n.exists(oldKey)) { + return Component.translatable(oldKey); + } + } + } + } + return Component.translatable(key); } } diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingConfig.java b/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingConfig.java deleted file mode 100644 index ecb0fc6..0000000 --- a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.github.scotsguy.nowplaying; - -import me.shedaniel.autoconfig.ConfigData; -import me.shedaniel.autoconfig.annotation.Config; -import me.shedaniel.autoconfig.annotation.ConfigEntry; -import me.shedaniel.clothconfig2.gui.entries.SelectionListEntry; -import org.jetbrains.annotations.NotNull; - -@Config(name = "now-playing") -@SuppressWarnings("unused") -public class NowPlayingConfig implements ConfigData { - @ConfigEntry.Gui.Tooltip - @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) - public Style musicStyle = Style.Toast; - - @ConfigEntry.Gui.Tooltip - @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) - public Style jukeboxStyle = Style.Hotbar; - - @ConfigEntry.Gui.Tooltip - public boolean silenceWoosh = false; - - public enum Style implements SelectionListEntry.Translatable { - Hotbar { - @Override - public @NotNull String getKey() { - return "now_playing.config.style.hotbar"; - } - }, - Toast { - @Override - public @NotNull String getKey() { - return "now_playing.config.style.toast"; - } - }, - Disabled { - @Override - public @NotNull String getKey() { - return "now_playing.config.style.disabled"; - } - } - } -} diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingListener.java b/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingListener.java deleted file mode 100644 index d701681..0000000 --- a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingListener.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.scotsguy.nowplaying; - -import me.shedaniel.autoconfig.AutoConfig; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.SoundEventListener; -import net.minecraft.client.sounds.WeighedSoundEvents; -import net.minecraft.network.chat.Component; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.RecordItem; - -@Environment(EnvType.CLIENT) -public class NowPlayingListener implements SoundEventListener { - @Override - public void onPlaySound(SoundInstance sound, WeighedSoundEvents soundSet) { - if (sound.getSource() == SoundSource.MUSIC) { - Component name = Util.getSoundName(sound); - if (name == null) return; - - NowPlayingConfig config = AutoConfig.getConfigHolder(NowPlayingConfig.class).getConfig(); - - if (config.musicStyle == NowPlayingConfig.Style.Toast) { - Minecraft.getInstance().getToasts().addToast(new NowPlayingToast(name)); - } else if (config.musicStyle == NowPlayingConfig.Style.Hotbar) { - Minecraft.getInstance().gui.setOverlayMessage(Component.translatable("record.nowPlaying", name), true); - } - } else if (sound.getSource() == SoundSource.RECORDS) { - NowPlayingConfig config = AutoConfig.getConfigHolder(NowPlayingConfig.class).getConfig(); - if (config.jukeboxStyle != NowPlayingConfig.Style.Toast) return; - - RecordItem disc = Util.getDiscFromSound(sound); - if (disc == null) return; - - Minecraft.getInstance().getToasts().addToast(new NowPlayingToast(disc.getDisplayName(), new ItemStack(disc))); - - } - - } -} diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingToast.java b/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingToast.java deleted file mode 100644 index f3c4c34..0000000 --- a/common/src/main/java/com/github/scotsguy/nowplaying/NowPlayingToast.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.scotsguy.nowplaying; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.toasts.Toast; -import net.minecraft.client.gui.components.toasts.ToastComponent; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class NowPlayingToast implements Toast { - private static final ResourceLocation BACKGROUND_SPRITE = new ResourceLocation("toast/recipe"); - - private final Component description; - private final ItemStack itemStack; - private boolean justUpdated; - private long startTime; - - private static final int TEXT_LEFT_MARGIN = 30; - private static final int TEXT_RIGHT_MARGIN = 7; - - public NowPlayingToast(Component description) { - this(description, new ItemStack(Items.MUSIC_DISC_CAT)); - } - - public NowPlayingToast(Component description, ItemStack itemStack) { - this.description = description; - this.itemStack = itemStack; - } - - @Override - public Visibility render(@NotNull GuiGraphics guiGraphics, @NotNull ToastComponent toastComponent, long startTime) { - Minecraft game = Minecraft.getInstance(); - - int width = this.width(); - int height = this.height(); - Font font = game.gui.getFont(); - List textLines = font.split(description, width - TEXT_LEFT_MARGIN - TEXT_RIGHT_MARGIN); - - if (width == 160 && textLines.size() <= 1) { - // Draw the whole toast from the texture - guiGraphics.blitSprite(BACKGROUND_SPRITE, 0, 0, width, height); - } else { - height = height + Math.max(0, textLines.size() - 1) * 12; - int bottomHeight = Math.min(4, height - 28); - // Draw the top border - this.renderBackgroundRow(guiGraphics, width, 0, 0, 28); - - // Draw plain background - for (int n = 28; n < height - bottomHeight; n += 10) { - this.renderBackgroundRow(guiGraphics, width, 16 /* middle */, n, Math.min(16, height - n - bottomHeight)); - } - - // Draw the bottom border - this.renderBackgroundRow(guiGraphics, width, 32 - bottomHeight, height - bottomHeight, bottomHeight); - } - // Draw "Now Playing" - guiGraphics.drawString(game.font, Component.translatable("now_playing.toast.now_playing"), TEXT_LEFT_MARGIN, 7, -11534256, false); - - // Draw song title - for (int i = 0; i < textLines.size(); ++i) { - guiGraphics.drawString(game.font, textLines.get(i), TEXT_LEFT_MARGIN, (18 + i * 12), -16777216, false); - } - - // Draw icon - guiGraphics.renderFakeItem(itemStack, 9, (height / 2) - (16 / 2)); - - return startTime - this.startTime >= 5000L ? Toast.Visibility.HIDE : Toast.Visibility.SHOW; - } - - private void renderBackgroundRow(GuiGraphics guiGraphics, int i, int vOffset, int y, int vHeight) { - int uWidth = vOffset == 0 ? 20 : 5; - int n = Math.min(60, i - uWidth); - - guiGraphics.blitSprite(BACKGROUND_SPRITE, 160, 32, 0, vOffset, 0, y, uWidth, vHeight); - - for (int o = uWidth; o < i - n; o += 64) { - guiGraphics.blitSprite(BACKGROUND_SPRITE, 160, 32, 32, vOffset, o, y, Math.min(64, i - o - n), vHeight); - } - - guiGraphics.blitSprite(BACKGROUND_SPRITE, 160, 32, 160 - n, vOffset, i - n, y, n, vHeight); - } -} - - diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/Util.java b/common/src/main/java/com/github/scotsguy/nowplaying/Util.java deleted file mode 100644 index 3919748..0000000 --- a/common/src/main/java/com/github/scotsguy/nowplaying/Util.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.scotsguy.nowplaying; - -import net.minecraft.client.resources.language.I18n; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.item.RecordItem; -import com.github.scotsguy.nowplaying.mixin.RecordItemAccessor; - -public class Util { - public static Component getSoundName(SoundInstance instance) { - String soundLocation = instance.getSound().getLocation().toString(); - if (soundLocation.startsWith("minecraft:music/") || I18n.exists("now_playing.sound." + soundLocation)) { - return Component.translatable("now_playing.sound." + soundLocation); - } - return null; - } - public static RecordItem getDiscFromSound(SoundInstance instance) { - for (SoundEvent event : RecordItemAccessor.getDiscs().keySet()) { - if (event.getLocation().equals(instance.getLocation())) { - return RecordItem.getBySound(event); - } - } - return null; - } -} diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/command/Commands.java b/common/src/main/java/com/github/scotsguy/nowplaying/command/Commands.java new file mode 100644 index 0000000..2ac6f02 --- /dev/null +++ b/common/src/main/java/com/github/scotsguy/nowplaying/command/Commands.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022-2026 AppleTheGolden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.github.scotsguy.nowplaying.command; + +import com.github.scotsguy.nowplaying.NowPlaying; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.client.Minecraft; +import net.minecraft.commands.CommandBuildContext; + +import static net.minecraft.commands.Commands.literal; + +@SuppressWarnings("unchecked") +public class Commands extends CommandDispatcher { + public void register(Minecraft mc, CommandDispatcher dispatcher, CommandBuildContext buildCtx) { + dispatcher.register((LiteralArgumentBuilder)literal("nowplaying") + .executes(ctx -> { + NowPlaying.displayLastMusic(); + return Command.SINGLE_SUCCESS; + }) + ); + } +} diff --git a/common/src/main/java/com/github/scotsguy/nowplaying/config/Config.java b/common/src/main/java/com/github/scotsguy/nowplaying/config/Config.java new file mode 100644 index 0000000..040aa8c --- /dev/null +++ b/common/src/main/java/com/github/scotsguy/nowplaying/config/Config.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2022-2026 AppleTheGolden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.github.scotsguy.nowplaying.config; + +import com.github.scotsguy.nowplaying.NowPlaying; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import static com.github.scotsguy.nowplaying.util.Localization.localized; + +public class Config { + private static final Path DIR_PATH = Path.of("config"); + private static final String FILE_NAME = NowPlaying.MOD_ID + ".json"; + private static final String BACKUP_FILE_NAME = NowPlaying.MOD_ID + ".unreadable.json"; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + // Options + + public final Options options = new Options(); + + public static Options options() { + return Config.get().options; + } + + public static class Options { + public static final boolean onlyKeybindDefault = false; + public boolean onlyKeybind = onlyKeybindDefault; + + public static final Style musicStyleDefault = Style.Toast; + public Style musicStyle = musicStyleDefault; + + public static final Style jukeboxStyleDefault = Style.Hotbar; + public Style jukeboxStyle = jukeboxStyleDefault; + + public static final boolean fallbackToastDefault = true; + public boolean fallbackToast = fallbackToastDefault; + + public static final boolean silenceWooshDefault = true; + public boolean silenceWoosh = silenceWooshDefault; + + public static final float toastScaleDefault = 1.0F; + public float toastScale = toastScaleDefault; + + public static final boolean simpleToastDefault = false; + public boolean simpleToast = simpleToastDefault; + + public static final boolean darkToastDefault = false; + public boolean darkToast = darkToastDefault; + + public static final int toastTimeDefault = 5; + public int toastTime = toastTimeDefault; + + public static final int hotbarTimeDefault = 3; + public int hotbarTime = hotbarTimeDefault; + + public static final boolean narrateDefault = true; + public boolean narrate = narrateDefault; + + public enum Style { + Toast, + Hotbar, + Disabled; + + public static Component name(Enum