first commit

This commit is contained in:
2026-09-12 13:23:01 +00:00
parent 3d98459187
commit 9fa534b20e
1905 changed files with 303180 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
.project
.classpath
.idea/
*.iml
target/
logs/
log/
test-output/
hexid.txt
.vscode/
BIN
View File
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
wrapperVersion=3.3.4
distributionType=script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar
@@ -0,0 +1,6 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding//src/test/resources=UTF-8
encoding/<project>=UTF-8
+4
View File
@@ -0,0 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=true
org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
+525
View File
@@ -0,0 +1,525 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.notowning=org.eclipse.jdt.annotation.NotOwning
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.annotation.owning=org.eclipse.jdt.annotation.Owning
org.eclipse.jdt.core.compiler.annotation.resourceanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=25
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=25
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompatibleOwningContract=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.insufficientResourceAnalysis=warning
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLambdaParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=25
org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns=false
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=true
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
org.eclipse.jdt.core.formatter.align_with_spaces=false
org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=0
org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0
org.eclipse.jdt.core.formatter.alignment_for_assignment=4
org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=0
org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=0
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=49
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=49
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon=0
org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_record_components=0
org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=85
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=84
org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=48
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow=0
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false
org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags=false
org.eclipse.jdt.core.formatter.comment.line_length=300
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=1
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_line_comments=false
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=false
org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true
org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.lineSplit=320
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
org.eclipse.jdt.core.formatter.tabulation.char=tab
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.text_block_indentation=0
org.eclipse.jdt.core.formatter.use_on_off_tags=true
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator=false
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
+147
View File
@@ -0,0 +1,147 @@
cleanup.add_all=true
cleanup.add_default_serial_version_id=false
cleanup.add_generated_serial_version_id=true
cleanup.add_missing_annotations=true
cleanup.add_missing_deprecated_annotations=true
cleanup.add_missing_methods=true
cleanup.add_missing_nls_tags=false
cleanup.add_missing_override_annotations=true
cleanup.add_missing_override_annotations_interface_methods=true
cleanup.add_serial_version_id=true
cleanup.also_simplify_lambda=true
cleanup.always_use_blocks=true
cleanup.always_use_parentheses_in_expressions=true
cleanup.always_use_this_for_non_static_field_access=false
cleanup.always_use_this_for_non_static_method_access=false
cleanup.array_with_curly=false
cleanup.arrays_fill=false
cleanup.bitwise_conditional_expression=false
cleanup.boolean_literal=true
cleanup.boolean_value_rather_than_comparison=true
cleanup.break_loop=false
cleanup.collection_cloning=false
cleanup.comparing_on_criteria=false
cleanup.comparison_statement=true
cleanup.controlflow_merge=false
cleanup.convert_functional_interfaces=true
cleanup.convert_to_enhanced_for_loop=true
cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
cleanup.convert_to_switch_expressions=true
cleanup.correct_indentation=false
cleanup.do_while_rather_than_while=true
cleanup.double_negation=true
cleanup.else_if=false
cleanup.embedded_if=false
cleanup.evaluate_nullable=false
cleanup.extract_increment=false
cleanup.format_source_code=true
cleanup.format_source_code_changes_only=false
cleanup.hash=false
cleanup.if_condition=true
cleanup.insert_inferred_type_arguments=false
cleanup.instanceof=true
cleanup.instanceof_keyword=true
cleanup.invert_equals=false
cleanup.join=true
cleanup.lazy_logical_operator=true
cleanup.make_local_variable_final=false
cleanup.make_parameters_final=false
cleanup.make_private_fields_final=true
cleanup.make_type_abstract_if_missing_method=false
cleanup.make_variable_declarations_final=true
cleanup.map_cloning=false
cleanup.merge_conditional_blocks=false
cleanup.multi_catch=false
cleanup.never_use_blocks=false
cleanup.never_use_parentheses_in_expressions=false
cleanup.no_string_creation=true
cleanup.no_super=true
cleanup.number_suffix=false
cleanup.objects_equals=false
cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
cleanup.operand_factorization=false
cleanup.organize_imports=true
cleanup.overridden_assignment=false
cleanup.overridden_assignment_move_decl=true
cleanup.plain_replacement=true
cleanup.precompile_regex=true
cleanup.primitive_comparison=true
cleanup.primitive_parsing=true
cleanup.primitive_rather_than_wrapper=true
cleanup.primitive_serialization=true
cleanup.pull_out_if_from_if_else=true
cleanup.pull_up_assignment=false
cleanup.push_down_negation=false
cleanup.qualify_static_field_accesses_with_declaring_class=false
cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
cleanup.qualify_static_member_accesses_with_declaring_class=true
cleanup.qualify_static_method_accesses_with_declaring_class=false
cleanup.reduce_indentation=false
cleanup.redundant_comparator=true
cleanup.redundant_falling_through_block_end=false
cleanup.remove_private_constructors=true
cleanup.remove_redundant_modifiers=true
cleanup.remove_redundant_semicolons=true
cleanup.remove_redundant_type_arguments=true
cleanup.remove_trailing_whitespaces=false
cleanup.remove_trailing_whitespaces_all=true
cleanup.remove_trailing_whitespaces_ignore_empty=true
cleanup.remove_unnecessary_array_creation=false
cleanup.remove_unnecessary_casts=true
cleanup.remove_unnecessary_nls_tags=true
cleanup.remove_unused_imports=true
cleanup.remove_unused_local_variables=true
cleanup.remove_unused_method_parameters=false
cleanup.remove_unused_private_fields=true
cleanup.remove_unused_private_members=true
cleanup.remove_unused_private_methods=true
cleanup.remove_unused_private_types=true
cleanup.return_expression=false
cleanup.simplify_lambda_expression_and_method_ref=true
cleanup.single_used_field=false
cleanup.sort_members=false
cleanup.sort_members_all=false
cleanup.standard_comparison=false
cleanup.static_inner_class=true
cleanup.strictly_equal_or_different=false
cleanup.stringbuffer_to_stringbuilder=true
cleanup.stringbuilder=true
cleanup.stringbuilder_for_local_vars=true
cleanup.stringconcat_stringbuffer_stringbuilder=false
cleanup.stringconcat_to_textblock=false
cleanup.substring=true
cleanup.switch=false
cleanup.system_property=false
cleanup.system_property_boolean=false
cleanup.system_property_file_encoding=false
cleanup.system_property_file_separator=false
cleanup.system_property_line_separator=false
cleanup.system_property_path_separator=false
cleanup.ternary_operator=false
cleanup.try_with_resource=false
cleanup.unlooped_while=false
cleanup.unreachable_block=false
cleanup.use_anonymous_class_creation=false
cleanup.use_autoboxing=false
cleanup.use_blocks=true
cleanup.use_blocks_only_for_return_and_throw=false
cleanup.use_directly_map_method=true
cleanup.use_lambda=true
cleanup.use_parentheses_in_expressions=true
cleanup.use_string_is_blank=true
cleanup.use_this_for_non_static_field_access=true
cleanup.use_this_for_non_static_field_access_only_if_necessary=true
cleanup.use_this_for_non_static_method_access=true
cleanup.use_this_for_non_static_method_access_only_if_necessary=true
cleanup.use_unboxing=false
cleanup.use_var=false
cleanup.useless_continue=false
cleanup.useless_return=false
cleanup.valueof_rather_than_instantiation=true
cleanup_profile=_L2J Clean Up v0.5
cleanup_settings_version=2
eclipse.preferences.version=1
formatter_profile=_L2J Formatter v0.9
formatter_settings_version=23
org.eclipse.jdt.ui.text.custom_code_templates=
+4
View File
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
+636
View File
@@ -0,0 +1,636 @@
# GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 [Free Software Foundation, Inc.](http://fsf.org/)
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for software and
other kinds of works.
The licenses for most software and other practical works are designed to take
away your freedom to share and change the works. By contrast, the GNU General
Public License is intended to guarantee your freedom to share and change all
versions of a program--to make sure it remains free software for all its users.
We, the Free Software Foundation, use the GNU General Public License for most
of our software; it applies also to any other work released this way by its
authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for them if you wish), that you
receive source code or can get it if you want it, that you can change the
software or use pieces of it in new free programs, and that you know you can do
these things.
To protect your rights, we need to prevent others from denying you these rights
or asking you to surrender the rights. Therefore, you have certain
responsibilities if you distribute copies of the software, or if you modify it:
responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for
a fee, you must pass on to the recipients the same freedoms that you received.
You must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps:
1. assert copyright on the software, and
2. offer you this License giving you legal permission to copy, distribute
and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that
there is no warranty for this free software. For both users' and authors' sake,
the GPL requires that modified versions be marked as changed, so that their
problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified
versions of the software inside them, although the manufacturer can do so. This
is fundamentally incompatible with the aim of protecting users' freedom to
change the software. The systematic pattern of such abuse occurs in the area of
products for individuals to use, which is precisely where it is most
unacceptable. Therefore, we have designed this version of the GPL to prohibit
the practice for those products. If such problems arise substantially in other
domains, we stand ready to extend this provision to those domains in future
versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States
should not allow patents to restrict development and use of software on
general-purpose computers, but in those that do, we wish to avoid the special
danger that patents applied to a free program could make it effectively
proprietary. To prevent this, the GPL assures that patents cannot be used to
render the program non-free.
The precise terms and conditions for copying, distribution and modification
follow.
## TERMS AND CONDITIONS
### 0. Definitions.
*This License* refers to version 3 of the GNU General Public License.
*Copyright* also means copyright-like laws that apply to other kinds of works,
such as semiconductor masks.
*The Program* refers to any copyrightable work licensed under this License.
Each licensee is addressed as *you*. *Licensees* and *recipients* may be
individuals or organizations.
To *modify* a work means to copy from or adapt all or part of the work in a
fashion requiring copyright permission, other than the making of an exact copy.
The resulting work is called a *modified version* of the earlier work or a work
*based on* the earlier work.
A *covered work* means either the unmodified Program or a work based on the
Program.
To *propagate* a work means to do anything with it that, without permission,
would make you directly or secondarily liable for infringement under applicable
copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as well.
To *convey* a work means any kind of propagation that enables other parties to
make or receive copies. Mere interaction with a user through a computer
network, with no transfer of a copy, is not conveying.
An interactive user interface displays *Appropriate Legal Notices* to the
extent that it includes a convenient and prominently visible feature that
1. displays an appropriate copyright notice, and
2. tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the work
under this License, and how to view a copy of this License.
If the interface presents a list of user commands or options, such as a menu, a
prominent item in the list meets this criterion.
### 1. Source Code.
The *source code* for a work means the preferred form of the work for making
modifications to it. *Object code* means any non-source form of a work.
A *Standard Interface* means an interface that either is an official standard
defined by a recognized standards body, or, in the case of interfaces specified
for a particular programming language, one that is widely used among developers
working in that language.
The *System Libraries* of an executable work include anything, other than the
work as a whole, that (a) is included in the normal form of packaging a Major
Component, but which is not part of that Major Component, and (b) serves only
to enable use of the work with that Major Component, or to implement a Standard
Interface for which an implementation is available to the public in source code
form. A *Major Component*, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (if any) on
which the executable work runs, or a compiler used to produce the work, or an
object code interpreter used to run it.
The *Corresponding Source* for a work in object code form means all the source
code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose
tools or generally available free programs which are used unmodified in
performing those activities but which are not part of the work. For example,
Corresponding Source includes interface definition files associated with source
files for the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require, such as
by intimate data communication or control flow between those subprograms and
other parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on
the Program, and are irrevocable provided the stated conditions are met. This
License explicitly affirms your unlimited permission to run the unmodified
Program. The output from running a covered work is covered by this License only
if the output, given its content, constitutes a covered work. This License
acknowledges your rights of fair use or other equivalent, as provided by
copyright law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey
covered works to others for the sole purpose of having them make modifications
exclusively for you, or provide you with facilities for running those works,
provided that you comply with the terms of this License in conveying all
material for which you do not control copyright. Those thus making or running
the covered works for you must do so exclusively on your behalf, under your
direction and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes it
unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure
under any applicable law fulfilling obligations under article 11 of the WIPO
copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention is
effected by exercising rights under this License with respect to the covered
work, and you disclaim any intention to limit operation or modification of the
work as a means of enforcing, against the work's users, your or third parties'
legal rights to forbid circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it,
in any medium, provided that you conspicuously and appropriately publish on
each copy an appropriate copyright notice; keep intact all notices stating that
this License and any non-permissive terms added in accord with section 7 apply
to the code; keep intact all notices of the absence of any warranty; and give
all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may
offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it
from the Program, in the form of source code under the terms of section 4,
provided that you also meet all of these conditions:
- a) The work must carry prominent notices stating that you modified it, and
giving a relevant date.
- b) The work must carry prominent notices stating that it is released under
this License and any conditions added under section 7. This requirement
modifies the requirement in section 4 to *keep intact all notices*.
- c) You must license the entire work, as a whole, under this License to
anyone who comes into possession of a copy. This License will therefore
apply, along with any applicable section 7 additional terms, to the whole
of the work, and all its parts, regardless of how they are packaged. This
License gives no permission to license the work in any other way, but it
does not invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your work need
not make them do so.
A compilation of a covered work with other separate and independent works,
which are not by their nature extensions of the covered work, and which are not
combined with it such as to form a larger program, in or on a volume of a
storage or distribution medium, is called an *aggregate* if the compilation and
its resulting copyright are not used to limit the access or legal rights of the
compilation's users beyond what the individual works permit. Inclusion of a
covered work in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4
and 5, provided that you also convey the machine-readable Corresponding Source
under the terms of this License, in one of these ways:
- a) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by the Corresponding Source
fixed on a durable physical medium customarily used for software
interchange.
- b) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by a written offer, valid for
at least three years and valid for as long as you offer spare parts or
customer support for that product model, to give anyone who possesses the
object code either
1. a copy of the Corresponding Source for all the software in the product
that is covered by this License, on a durable physical medium
customarily used for software interchange, for a price no more than your
reasonable cost of physically performing this conveying of source, or
2. access to copy the Corresponding Source from a network server at no
charge.
- c) Convey individual copies of the object code with a copy of the written
offer to provide the Corresponding Source. This alternative is allowed only
occasionally and noncommercially, and only if you received the object code
with such an offer, in accord with subsection 6b.
- d) Convey the object code by offering access from a designated place
(gratis or for a charge), and offer equivalent access to the Corresponding
Source in the same way through the same place at no further charge. You
need not require recipients to copy the Corresponding Source along with the
object code. If the place to copy the object code is a network server, the
Corresponding Source may be on a different server operated by you or a
third party) that supports equivalent copying facilities, provided you
maintain clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the Corresponding
Source, you remain obligated to ensure that it is available for as long as
needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission, provided you
inform other peers where the object code and Corresponding Source of the
work are being offered to the general public at no charge under subsection
6d.
A separable portion of the object code, whose source code is excluded from the
Corresponding Source as a System Library, need not be included in conveying the
object code work.
A *User Product* is either
1. a *consumer product*, which means any tangible personal property which is
normally used for personal, family, or household purposes, or
2. anything designed or sold for incorporation into a dwelling.
In determining whether a product is a consumer product, doubtful cases shall be
resolved in favor of coverage. For a particular product received by a
particular user, *normally used* refers to a typical or common use of that
class of product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected to use,
the product. A product is a consumer product regardless of whether the product
has substantial commercial, industrial or non-consumer uses, unless such uses
represent the only significant mode of use of the product.
*Installation Information* for a User Product means any methods, procedures,
authorization keys, or other information required to install and execute
modified versions of a covered work in that User Product from a modified
version of its Corresponding Source. The information must suffice to ensure
that the continued functioning of the modified object code is in no case
prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as part of a
transaction in which the right of possession and use of the User Product is
transferred to the recipient in perpetuity or for a fixed term (regardless of
how the transaction is characterized), the Corresponding Source conveyed under
this section must be accompanied by the Installation Information. But this
requirement does not apply if neither you nor any third party retains the
ability to install modified object code on the User Product (for example, the
work has been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates for a
work that has been modified or installed by the recipient, or for the User
Product in which it has been modified or installed. Access to a network may be
denied when the modification itself materially and adversely affects the
operation of the network or violates the rules and protocols for communication
across the network.
Corresponding Source conveyed, and Installation Information provided, in accord
with this section must be in a format that is publicly documented (and with an
implementation available to the public in source code form), and must require
no special password or key for unpacking, reading or copying.
### 7. Additional Terms.
*Additional permissions* are terms that supplement the terms of this License by
making exceptions from one or more of its conditions. Additional permissions
that are applicable to the entire Program shall be treated as though they were
included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part may
be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when
you modify the work.) You may place additional permissions on material, added
by you to a covered work, for which you have or can give appropriate copyright
permission.
Notwithstanding any other provision of this License, for material you add to a
covered work, you may (if authorized by the copyright holders of that material)
supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the terms of
sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed
by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in reasonable
ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors or authors
of the material; or
- e) Declining to grant rights under trademark law for use of some trade
names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that material by
anyone who conveys the material (or modified versions of it) with
contractual assumptions of liability to the recipient, for any liability
that these contractual assumptions directly impose on those licensors and
authors.
All other non-permissive additional terms are considered *further restrictions*
within the meaning of section 10. If the Program as you received it, or any
part of it, contains a notice stating that it is governed by this License along
with a term that is a further restriction, you may remove that term. If a
license document contains a further restriction but permits relicensing or
conveying under this License, you may add to a covered work material governed
by the terms of that license document, provided that the further restriction
does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place,
in the relevant source files, a statement of the additional terms that apply to
those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a
separately written license, or stated as exceptions; the above requirements
apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly provided
under this License. Any attempt otherwise to propagate or modify it is void,
and will automatically terminate your rights under this License (including any
patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a
particular copyright holder is reinstated
- a) provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and
- b) permanently, if the copyright holder fails to notify you of the
violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated
permanently if the copyright holder notifies you of the violation by some
reasonable means, this is the first time you have received notice of violation
of this License (for any work) from that copyright holder, and you cure the
violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses
of parties who have received copies or rights from you under this License. If
your rights have been terminated and not permanently reinstated, you do not
qualify to receive new licenses for the same material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy
of the Program. Ancillary propagation of a covered work occurring solely as a
consequence of using peer-to-peer transmission to receive a copy likewise does
not require acceptance. However, nothing other than this License grants you
permission to propagate or modify any covered work. These actions infringe
copyright if you do not accept this License. Therefore, by modifying or
propagating a covered work, you indicate your acceptance of this License to do
so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a
license from the original licensors, to run, modify and propagate that work,
subject to this License. You are not responsible for enforcing compliance by
third parties with this License.
An *entity transaction* is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered work
results from an entity transaction, each party to that transaction who receives
a copy of the work also receives whatever licenses to the work the party's
predecessor in interest had or could give under the previous paragraph, plus a
right to possession of the Corresponding Source of the work from the
predecessor in interest, if the predecessor has it or can get it with
reasonable efforts.
You may not impose any further restrictions on the exercise of the rights
granted or affirmed under this License. For example, you may not impose a
license fee, royalty, or other charge for exercise of rights granted under this
License, and you may not initiate litigation (including a cross-claim or
counterclaim in a lawsuit) alleging that any patent claim is infringed by
making, using, selling, offering for sale, or importing the Program or any
portion of it.
### 11. Patents.
A *contributor* is a copyright holder who authorizes use under this License of
the Program or a work on which the Program is based. The work thus licensed is
called the contributor's *contributor version*.
A contributor's *essential patent claims* are all patent claims owned or
controlled by the contributor, whether already acquired or hereafter acquired,
that would be infringed by some manner, permitted by this License, of making,
using, or selling its contributor version, but do not include claims that would
be infringed only as a consequence of further modification of the contributor
version. For purposes of this definition, *control* includes the right to grant
patent sublicenses in a manner consistent with the requirements of this
License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
license under the contributor's essential patent claims, to make, use, sell,
offer for sale, import and otherwise run, modify and propagate the contents of
its contributor version.
In the following three paragraphs, a *patent license* is any express agreement
or commitment, however denominated, not to enforce a patent (such as an express
permission to practice a patent or covenant not to sue for patent
infringement). To *grant* such a patent license to a party means to make such
an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free of
charge and under the terms of this License, through a publicly available
network server or other readily accessible means, then you must either
1. cause the Corresponding Source to be so available, or
2. arrange to deprive yourself of the benefit of the patent license for this
particular work, or
3. arrange, in a manner consistent with the requirements of this License, to
extend the patent license to downstream recipients.
*Knowingly relying* means you have actual knowledge that, but for the patent
license, your conveying the covered work in a country, or your recipient's use
of the covered work in a country, would infringe one or more identifiable
patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you
convey, or propagate by procuring conveyance of, a covered work, and grant a
patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work,
then the patent license you grant is automatically extended to all recipients
of the covered work and works based on it.
A patent license is *discriminatory* if it does not include within the scope of
its coverage, prohibits the exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically granted under this License.
You may not convey a covered work if you are a party to an arrangement with a
third party that is in the business of distributing software, under which you
make payment to the third party based on the extent of your activity of
conveying the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory patent
license
- a) in connection with copies of the covered work conveyed by you (or copies
made from those copies), or
- b) primarily for and in connection with specific products or compilations
that contain the covered work, unless you entered into that arrangement, or
that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available to
you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not excuse
you from the conditions of this License. If you cannot convey a covered work so
as to satisfy simultaneously your obligations under this License and any other
pertinent obligations, then as a consequence you may not convey it at all. For
example, if you agree to terms that obligate you to collect a royalty for
further conveying from those to whom you convey the Program, the only way you
could satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to
link or combine any covered work with a work licensed under version 3 of the
GNU Affero General Public License into a single combined work, and to convey
the resulting work. The terms of this License will continue to apply to the
part which is the covered work, but the special requirements of the GNU Affero
General Public License, section 13, concerning interaction through a network
will apply to the combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems
or concerns.
Each version is given a distinguishing version number. If the Program specifies
that a certain numbered version of the GNU General Public License *or any later
version* applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by
the Free Software Foundation. If the Program does not specify a version number
of the GNU General Public License, you may choose any version ever published by
the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the
GNU General Public License can be used, that proxy's public statement of
acceptance of a version permanently authorizes you to choose that version for
the Program.
Later license versions may give you additional or different permissions.
However, no additional obligations are imposed on any author or copyright
holder as a result of your choosing to follow a later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
PARTIES PROVIDE THE PROGRAM *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot
be given local legal effect according to their terms, reviewing courts shall
apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption of
liability accompanies a copy of the Program in return for a fee.
## END OF TERMS AND CONDITIONS ###
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively state the exclusion
of warranty; and each file should have at least the *copyright* line and a
pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like
this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w` and `show c` should show the appropriate
parts of the General Public License. Of course, your program's commands might
be different; for a GUI interface, you would use an *about box*.
You should also get your employer (if you work as a programmer) or school, if
any, to sign a *copyright disclaimer* for the program, if necessary. For more
information on this, and how to apply and follow the GNU GPL, see
[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
The GNU General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may consider
it more useful to permit linking proprietary applications with the library. If
this is what you want to do, use the GNU Lesser General Public License instead
of this License. But first, please read
[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html).
+22
View File
@@ -0,0 +1,22 @@
image: eclipse-temurin:25-jdk-ubi10-minimal
pipelines:
default:
- step:
caches:
- maven
script:
- /bin/sh mvnw clean install
artifacts:
- target/*.zip
- step:
deployment: Production
script:
- pipe: atlassian/ftp-deploy:0.7.1
variables:
USER: $FTP_USER
PASSWORD: $FTP_PASSWORD
SERVER: $FTP_HOST
REMOTE_PATH: $FTP_REMOTE_PATH
LOCAL_PATH: target/
SET_ARGS: "ftp:ssl-allow yes ftp:ssl-force yes ftp:ssl-protect-data yes ssl:verify-certificate false"
Vendored
+338
View File
@@ -0,0 +1,338 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ]; then
if [ -f /usr/local/etc/mavenrc ]; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ]; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ]; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false
darwin=false
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true ;;
Darwin*)
darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"
export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"
export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ]; then
if [ -r /etc/gentoo-release ]; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
&& JAVA_HOME="$(
cd "$JAVA_HOME" || (
echo "cannot cd into $JAVA_HOME." >&2
exit 1
)
pwd
)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin; then
javaHome="$(dirname "$javaExecutable")"
javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "$javaExecutable")"
fi
javaHome="$(dirname "$javaExecutable")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ]; then
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(
\unset -f command 2>/dev/null
\command -v java
)"
fi
fi
if [ ! -x "$JAVACMD" ]; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ]; then
echo "Warning: JAVA_HOME environment variable is not set." >&2
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]; then
echo "Path not specified to find_maven_basedir" >&2
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ]; do
if [ -d "$wdir"/.mvn ]; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(
cd "$wdir/.." || exit 1
pwd
)
fi
# end of workaround
done
printf '%s' "$(
cd "$basedir" || exit 1
pwd
)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' <"$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
fi
while IFS="=" read -r key value; do
case "$key" in wrapperUrl)
wrapperUrl=$(trim "${value-}")
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget >/dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl >/dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in wrapperSha256Sum)
wrapperSha256Sum=$(trim "${value-}")
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] \
&& MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
Vendored
+206
View File
@@ -0,0 +1,206 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo. >&2
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo. >&2
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo. >&2
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo. >&2
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
+203
View File
@@ -0,0 +1,203 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.l2jserver</groupId>
<artifactId>l2j-server-game</artifactId>
<version>2.6.3.0-SNAPSHOT</version>
<name>L2J Game Server</name>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- External -->
<slf4j.version>2.0.17</slf4j.version>
<log4j.version>2.25.3</log4j.version>
<InMemoryJavaCompiler.version>1.3.0</InMemoryJavaCompiler.version>
<gson.version>2.13.2</gson.version>
<owner.version>1.0.12</owner.version>
<jda.version>5.3.0</jda.version>
<!-- L2J -->
<l2j-server-commons.version>2.6.9.0</l2j-server-commons.version>
<l2j-server-geo-driver.version>2.6.5.0</l2j-server-geo-driver.version>
<!-- Test -->
<junit-jupiter.version>6.0.1</junit-jupiter.version>
<mockito.version>5.21.0</mockito.version>
<assertj-core.version>3.27.6</assertj-core.version>
<!-- Plugins -->
<dependency-check-maven.level>8</dependency-check-maven.level>
<dependency-check-maven.version>12.1.9</dependency-check-maven.version>
<maven-assembly-plugin.version>3.8.0</maven-assembly-plugin.version>
<maven-compiler-plugin.version>3.14.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<build>
<finalName>l2jserver</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>-Xshare:off -javaagent:"${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar"</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<proc>full</proc>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>com.l2jserver.gameserver.GameServer</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/assembly/zip.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.mdkt.compiler</groupId>
<artifactId>InMemoryJavaCompiler</artifactId>
<version>${InMemoryJavaCompiler.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.aeonbits.owner</groupId>
<artifactId>owner-java8</artifactId>
<version>${owner.version}</version>
</dependency>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>${jda.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- L2J -->
<dependency>
<groupId>org.bitbucket.l2jserver</groupId>
<artifactId>l2j-server-commons</artifactId>
<version>${l2j-server-commons.version}</version>
</dependency>
<dependency>
<groupId>org.bitbucket.l2jserver</groupId>
<artifactId>l2j-server-geo-driver</artifactId>
<version>${l2j-server-geo-driver.version}</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>ci</id>
<build>
<plugins>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>${dependency-check-maven.version}</version>
<configuration>
<failBuildOnCVSS>${dependency-check-maven.level}</failBuildOnCVSS>
<assemblyAnalyzerEnabled>false</assemblyAnalyzerEnabled>
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
+43
View File
@@ -0,0 +1,43 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>zip</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>zip</format>
</formats>
<files>
<file>
<source>${project.build.directory}/l2jserver.jar</source>
<outputDirectory>.</outputDirectory>
</file>
<file>
<source>src/main/resources/GameServer_loop.sh</source>
<outputDirectory>.</outputDirectory>
</file>
<file>
<source>src/main/resources/startGameServer.bat</source>
<outputDirectory>.</outputDirectory>
</file>
<file>
<source>src/main/resources/startGameServer.sh</source>
<outputDirectory>.</outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>src/main/resources/</directory>
<includes>
<include>config/*</include>
</includes>
<outputDirectory>.</outputDirectory>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>libs</outputDirectory>
<excludes>
<exclude>${project.groupId}:${project.artifactId}:jar:*</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>
@@ -0,0 +1,35 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
/**
* Custom.
* @author Zoey76
* @version 2.6.2.0
*/
@Documented
@Retention(RUNTIME)
public @interface Custom {
}
@@ -0,0 +1,95 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.fortress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.model.L2Clan;
import com.l2jserver.gameserver.model.entity.Fort;
import com.l2jserver.gameserver.model.itemcontainer.Inventory;
/**
* Class managing periodical events with castle.
* @author Vice
* @since 2008
*/
public class FortUpdater implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(FortUpdater.class);
private final L2Clan _clan;
private final Fort _fort;
private int _runCount;
private final UpdaterType _updaterType;
public enum UpdaterType {
MAX_OWN_TIME, // gives fort back to NPC clan
PERIODIC_UPDATE // raise blood oath/supply level
}
public FortUpdater(Fort fort, L2Clan clan, int runCount, UpdaterType ut) {
_fort = fort;
_clan = clan;
_runCount = runCount;
_updaterType = ut;
}
@Override
public void run() {
try {
switch (_updaterType) {
case PERIODIC_UPDATE -> {
_runCount++;
if ((_fort.getOwnerClan() == null) || (_fort.getOwnerClan() != _clan)) {
return;
}
_fort.getOwnerClan().increaseBloodOathCount();
if (_fort.getFortState() == 2) {
if (_clan.getWarehouse().getAdena() >= fortress().getFeeForCastle()) {
_clan.getWarehouse().destroyItemByItemId("FS_fee_for_Castle", Inventory.ADENA_ID, fortress().getFeeForCastle(), null, null);
_fort.getContractedCastle().addToTreasuryNoTax(fortress().getFeeForCastle());
_fort.raiseSupplyLvL();
} else {
_fort.setFortState(1, 0);
}
}
_fort.saveFortVariables();
}
case MAX_OWN_TIME -> {
if ((_fort.getOwnerClan() == null) || (_fort.getOwnerClan() != _clan)) {
return;
}
if (_fort.getOwnedTime() > (fortress().getMaxKeepTime() * 3600)) {
_fort.removeOwner(true);
_fort.setFortState(0, 0);
}
}
}
} catch (Exception e) {
LOG.error("There has been a problem updating forts!", e);
}
}
public int getRunCount() {
return _runCount;
}
}
@@ -0,0 +1,538 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.customs;
import static com.l2jserver.gameserver.config.Configuration.database;
import static com.l2jserver.gameserver.config.Configuration.general;
import static com.l2jserver.gameserver.config.Configuration.geodata;
import static com.l2jserver.gameserver.config.Configuration.hexId;
import static com.l2jserver.gameserver.config.Configuration.mmo;
import static com.l2jserver.gameserver.config.Configuration.server;
import static com.l2jserver.gameserver.config.Configuration.telnet;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.UPnPService;
import com.l2jserver.commons.dao.ServerNameDAO;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.commons.util.IPv4Filter;
import com.l2jserver.commons.util.Util;
import com.l2jserver.gameserver.bbs.service.ForumsBBSManager;
import com.l2jserver.gameserver.cache.HtmCache;
import com.l2jserver.gameserver.dao.factory.impl.DAOFactory;
import com.l2jserver.gameserver.data.json.ExperienceData;
import com.l2jserver.gameserver.data.sql.impl.AnnouncementsTable;
import com.l2jserver.gameserver.data.sql.impl.CharNameTable;
import com.l2jserver.gameserver.data.sql.impl.CharSummonTable;
import com.l2jserver.gameserver.data.sql.impl.ClanTable;
import com.l2jserver.gameserver.data.sql.impl.CrestTable;
import com.l2jserver.gameserver.data.sql.impl.NpcBufferTable;
import com.l2jserver.gameserver.data.sql.impl.OfflineTradersTable;
import com.l2jserver.gameserver.data.sql.impl.SummonSkillsTable;
import com.l2jserver.gameserver.data.xml.impl.AdminData;
import com.l2jserver.gameserver.data.xml.impl.ArmorSetsData;
import com.l2jserver.gameserver.data.xml.impl.BuyListData;
import com.l2jserver.gameserver.data.xml.impl.CategoryData;
import com.l2jserver.gameserver.data.xml.impl.ClassListData;
import com.l2jserver.gameserver.data.xml.impl.DoorData;
import com.l2jserver.gameserver.data.xml.impl.EnchantItemData;
import com.l2jserver.gameserver.data.xml.impl.EnchantItemGroupsData;
import com.l2jserver.gameserver.data.xml.impl.EnchantItemHPBonusData;
import com.l2jserver.gameserver.data.xml.impl.EnchantItemOptionsData;
import com.l2jserver.gameserver.data.xml.impl.EnchantSkillGroupsData;
import com.l2jserver.gameserver.data.xml.impl.FishData;
import com.l2jserver.gameserver.data.xml.impl.FishingMonstersData;
import com.l2jserver.gameserver.data.xml.impl.FishingRodsData;
import com.l2jserver.gameserver.data.xml.impl.HennaData;
import com.l2jserver.gameserver.data.xml.impl.HitConditionBonusData;
import com.l2jserver.gameserver.data.xml.impl.InitialEquipmentData;
import com.l2jserver.gameserver.data.xml.impl.InitialShortcutData;
import com.l2jserver.gameserver.data.xml.impl.KarmaData;
import com.l2jserver.gameserver.data.xml.impl.MultisellData;
import com.l2jserver.gameserver.data.xml.impl.NpcData;
import com.l2jserver.gameserver.data.xml.impl.OptionData;
import com.l2jserver.gameserver.data.xml.impl.PetDataTable;
import com.l2jserver.gameserver.data.xml.impl.PlayerCreationPointData;
import com.l2jserver.gameserver.data.xml.impl.PlayerTemplateData;
import com.l2jserver.gameserver.data.xml.impl.PlayerXpPercentLostData;
import com.l2jserver.gameserver.data.xml.impl.RecipeData;
import com.l2jserver.gameserver.data.xml.impl.SecondaryAuthData;
import com.l2jserver.gameserver.data.xml.impl.SiegeScheduleData;
import com.l2jserver.gameserver.data.xml.impl.SkillLearnData;
import com.l2jserver.gameserver.data.xml.impl.SkillTreesData;
import com.l2jserver.gameserver.data.xml.impl.StaticObjectData;
import com.l2jserver.gameserver.data.xml.impl.TransformData;
import com.l2jserver.gameserver.data.xml.impl.UIData;
import com.l2jserver.gameserver.datatables.AugmentationData;
import com.l2jserver.gameserver.datatables.BotReportTable;
import com.l2jserver.gameserver.datatables.EventDroplist;
import com.l2jserver.gameserver.datatables.ItemTable;
import com.l2jserver.gameserver.datatables.SkillData;
import com.l2jserver.gameserver.datatables.SpawnTable;
import com.l2jserver.gameserver.handler.EffectHandler;
import com.l2jserver.gameserver.idfactory.IdFactory;
import com.l2jserver.gameserver.instancemanager.AirShipManager;
import com.l2jserver.gameserver.instancemanager.AntiFeedManager;
import com.l2jserver.gameserver.instancemanager.AuctionManager;
import com.l2jserver.gameserver.instancemanager.BoatManager;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.CastleManorManager;
import com.l2jserver.gameserver.instancemanager.ClanHallManager;
import com.l2jserver.gameserver.instancemanager.ClanHallSiegeManager;
import com.l2jserver.gameserver.instancemanager.CoupleManager;
import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
import com.l2jserver.gameserver.instancemanager.DayNightSpawnManager;
import com.l2jserver.gameserver.instancemanager.DimensionalRiftManager;
import com.l2jserver.gameserver.instancemanager.FortManager;
import com.l2jserver.gameserver.instancemanager.FortSiegeManager;
import com.l2jserver.gameserver.instancemanager.FourSepulchersManager;
import com.l2jserver.gameserver.instancemanager.GlobalVariablesManager;
import com.l2jserver.gameserver.instancemanager.GraciaSeedsManager;
import com.l2jserver.gameserver.instancemanager.GrandBossManager;
import com.l2jserver.gameserver.instancemanager.InstanceManager;
import com.l2jserver.gameserver.instancemanager.ItemAuctionManager;
import com.l2jserver.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jserver.gameserver.instancemanager.MailManager;
import com.l2jserver.gameserver.instancemanager.MapRegionManager;
import com.l2jserver.gameserver.instancemanager.MercTicketManager;
import com.l2jserver.gameserver.instancemanager.PetitionManager;
import com.l2jserver.gameserver.instancemanager.PunishmentManager;
import com.l2jserver.gameserver.instancemanager.QuestManager;
import com.l2jserver.gameserver.instancemanager.RaidBossPointsManager;
import com.l2jserver.gameserver.instancemanager.RaidBossSpawnManager;
import com.l2jserver.gameserver.instancemanager.SiegeManager;
import com.l2jserver.gameserver.instancemanager.TerritoryWarManager;
import com.l2jserver.gameserver.instancemanager.WalkingManager;
import com.l2jserver.gameserver.instancemanager.ZoneManager;
import com.l2jserver.gameserver.model.AutoSpawnHandler;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.PartyMatchRoomList;
import com.l2jserver.gameserver.model.PartyMatchWaitingList;
import com.l2jserver.gameserver.model.entity.Hero;
import com.l2jserver.gameserver.model.entity.TvTManager;
import com.l2jserver.gameserver.model.events.EventDispatcher;
import com.l2jserver.gameserver.model.olympiad.Olympiad;
import com.l2jserver.gameserver.network.L2GameClient;
import com.l2jserver.gameserver.network.L2GamePacketHandler;
import com.l2jserver.gameserver.network.NpcStringId;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.pathfinding.PathFinding;
import com.l2jserver.gameserver.script.faenor.FaenorScriptEngine;
import com.l2jserver.gameserver.scripting.ScriptEngineManager;
import com.l2jserver.gameserver.status.Status;
import com.l2jserver.gameserver.taskmanager.KnownListUpdateTaskManager;
import com.l2jserver.gameserver.taskmanager.TaskManager;
import com.l2jserver.gameserver.util.DeadLockDetector;
import com.l2jserver.mmocore.SelectorConfig;
import com.l2jserver.mmocore.SelectorThread;
public final class GameServer {
private static final Logger LOG = LoggerFactory.getLogger(GameServer.class);
private static final String DATAPACK = "-dp";
private static final String SCRIPT = "-s";
private static final String GEODATA = "-gd";
private final SelectorThread<L2GameClient> _selectorThread;
private final L2GamePacketHandler _gamePacketHandler;
private final DeadLockDetector _deadDetectThread;
public static GameServer gameServer;
public static final Calendar dateTimeServerStarted = Calendar.getInstance();
public GameServer() throws Exception {
final var serverLoadStart = startTimedSection("Database");
ConnectionFactory.builder() //
.withUrl(database().getURL()) //
.withUser(database().getUser()) //
.withPassword(database().getPassword()) //
.withMaxIdleTime(database().getMaxIdleTime()) //
.withMaxPoolSize(database().getMaxConnections()) //
.build();
DAOFactory.getInstance();
if (!IdFactory.getInstance().isInitialized()) {
LOG.error("Could not read object IDs from database. Please check your configuration.");
throw new Exception("Could not initialize the Id factory!");
}
endTimedSection("Database", serverLoadStart);
ThreadPoolManager.getInstance();
EventDispatcher.getInstance();
ScriptEngineManager.getInstance();
final var worldStart = startTimedSection("World");
GameTimeController.init();
InstanceManager.getInstance();
L2World.getInstance();
MapRegionManager.getInstance();
AnnouncementsTable.getInstance();
GlobalVariablesManager.getInstance();
endTimedSection("World", worldStart);
final var dataStart = startTimedSection("Data");
CategoryData.getInstance();
SecondaryAuthData.getInstance();
endTimedSection("Data", dataStart);
final var effectsStart = startTimedSection("Effects");
EffectHandler.getInstance().executeScript();
endTimedSection("Effects", effectsStart);
final var esgStart = startTimedSection("Enchant Skill Groups");
EnchantSkillGroupsData.getInstance();
endTimedSection("Enchant Skill Groups", esgStart);
final var skillTreesStart = startTimedSection("Skill Trees");
SkillTreesData.getInstance();
endTimedSection("Skill Trees", skillTreesStart);
final var skillsStart = startTimedSection("Skills");
SkillData.getInstance();
SummonSkillsTable.getInstance();
endTimedSection("Skills", skillsStart);
final var itemsStart = startTimedSection("Items");
ItemTable.getInstance();
EnchantItemGroupsData.getInstance();
EnchantItemData.getInstance();
EnchantItemOptionsData.getInstance();
OptionData.getInstance();
EnchantItemHPBonusData.getInstance();
MultisellData.getInstance();
RecipeData.getInstance();
ArmorSetsData.getInstance();
FishData.getInstance();
FishingMonstersData.getInstance();
FishingRodsData.getInstance();
HennaData.getInstance();
endTimedSection("Items", itemsStart);
final var charactersStart = startTimedSection("Characters");
ClassListData.getInstance();
InitialEquipmentData.getInstance();
InitialShortcutData.getInstance();
ExperienceData.getInstance();
PlayerXpPercentLostData.getInstance();
KarmaData.getInstance();
HitConditionBonusData.getInstance();
PlayerTemplateData.getInstance();
PlayerCreationPointData.getInstance();
CharNameTable.getInstance();
AdminData.getInstance();
RaidBossPointsManager.getInstance();
PetDataTable.getInstance();
CharSummonTable.getInstance().init();
endTimedSection("Characters", charactersStart);
final var bbsStart = startTimedSection("BBS");
if (general().enableCommunityBoard()) {
ForumsBBSManager.getInstance().load();
}
endTimedSection("BBS", bbsStart);
final var clansStart = startTimedSection("Clans");
ClanTable.getInstance();
ClanHallSiegeManager.getInstance();
ClanHallManager.getInstance();
AuctionManager.getInstance();
endTimedSection("Clans", clansStart);
final var geodataStart = startTimedSection("Geodata");
GeoData.getInstance();
if (geodata().getPathFinding() > 0) {
PathFinding.getInstance();
}
endTimedSection("Geodata", geodataStart);
final var npcsStart = startTimedSection("NPCs");
SkillLearnData.getInstance();
NpcData.getInstance();
WalkingManager.getInstance();
StaticObjectData.getInstance();
ZoneManager.getInstance();
DoorData.getInstance();
CastleManager.getInstance().loadInstances();
NpcBufferTable.getInstance();
GrandBossManager.getInstance().initZones();
EventDroplist.getInstance();
endTimedSection("NPCs", npcsStart);
final var auctionStart = startTimedSection("Auction Manager");
ItemAuctionManager.getInstance();
endTimedSection("Auction Manager", auctionStart);
final var olympiadStart = startTimedSection("Olympiad");
Olympiad.getInstance();
Hero.getInstance();
endTimedSection("Olympiad", olympiadStart);
final var sevenSignsStart = startTimedSection("Seven Signs");
SevenSigns.getInstance();
endTimedSection("Seven Signs", sevenSignsStart);
// Call to load caches
final var cacheStart = startTimedSection("Cache");
HtmCache.getInstance();
CrestTable.getInstance();
UIData.getInstance();
PartyMatchWaitingList.getInstance();
PartyMatchRoomList.getInstance();
PetitionManager.getInstance();
AugmentationData.getInstance();
CursedWeaponsManager.getInstance();
TransformData.getInstance();
BotReportTable.getInstance();
QuestManager.getInstance();
BoatManager.getInstance();
AirShipManager.getInstance();
GraciaSeedsManager.getInstance();
endTimedSection("Cache", cacheStart);
final var handlersStart = startTimedSection("Handlers");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/handlers/MasterHandler.java");
endTimedSection("Handlers", handlersStart);
final var aiStart = startTimedSection("AI");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/ai/AILoader.java");
endTimedSection("AI", aiStart);
final var instancesStart = startTimedSection("Instances");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/instances/InstanceLoader.java");
endTimedSection("Instances", instancesStart);
final var graciaStart = startTimedSection("Gracia");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/gracia/GraciaLoader.java");
endTimedSection("Gracia", graciaStart);
final var hellboundStart = startTimedSection("Hellbound");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/hellbound/HellboundLoader.java");
endTimedSection("Hellbound", hellboundStart);
final var questsStart = startTimedSection("Quests");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/quests/QuestLoader.java");
ScriptEngineManager.getInstance().executeScript("com/l2jserver/datapack/quests/TerritoryWarScripts/TerritoryWarSuperClass.java");
endTimedSection("Quests", questsStart);
final var scriptsStart = startTimedSection("Scripts");
ScriptEngineManager.getInstance().executeScriptList(new File(server().getDatapackRoot(), "data/scripts.cfg"));
endTimedSection("Scripts", scriptsStart);
final var buyListStart = startTimedSection("BuyLists");
BuyListData.getInstance().load();
endTimedSection("BuyLists", buyListStart);
final var spawnsStart = startTimedSection("Spawns");
SpawnTable.getInstance().load();
DayNightSpawnManager.getInstance().trim().notifyChangeMode();
FourSepulchersManager.getInstance().init();
DimensionalRiftManager.getInstance();
RaidBossSpawnManager.getInstance();
endTimedSection("Spawns", spawnsStart);
final var siegeStart = startTimedSection("Siege");
SiegeManager.getInstance().getSieges();
CastleManager.getInstance().activateInstances();
FortManager.getInstance().loadInstances();
FortManager.getInstance().activateInstances();
FortSiegeManager.getInstance();
SiegeScheduleData.getInstance();
TerritoryWarManager.getInstance();
CastleManorManager.getInstance();
MercTicketManager.getInstance();
endTimedSection("Siege", siegeStart);
final var otherStart = startTimedSection("Other");
if (general().saveDroppedItem()) {
ItemsOnGroundManager.getInstance();
}
if ((general().getAutoDestroyDroppedItemAfter() > 0) || (general().getAutoDestroyHerbTime() > 0)) {
ItemsAutoDestroy.getInstance();
}
MonsterRace.getInstance();
SevenSigns.getInstance().spawnSevenSignsNPC();
SevenSignsFestival.getInstance();
AutoSpawnHandler.getInstance();
FaenorScriptEngine.getInstance();
if (customs().allowWedding()) {
CoupleManager.getInstance();
}
TaskManager.getInstance();
AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID);
if (general().allowMail()) {
MailManager.getInstance();
}
PunishmentManager.getInstance();
Runtime.getRuntime().addShutdownHook(Shutdown.getInstance());
LOG.info("Free Object Ids remaining {}.", IdFactory.getInstance().size());
TvTManager.getInstance();
KnownListUpdateTaskManager.getInstance();
if ((customs().offlineTradeEnable() || customs().offlineCraftEnable()) && customs().restoreOffliners()) {
OfflineTradersTable.getInstance().restoreOfflineTraders();
}
// Multilingual Support
if (customs().multiLangEnable()) {
if (customs().multiLangSystemMessageEnable()) {
SystemMessageId.reloadLocalisations();
}
if (customs().multiLangNpcStringEnable()) {
NpcStringId.reloadLocalisations();
}
}
endTimedSection("Other", otherStart);
if (general().deadLockDetector()) {
_deadDetectThread = new DeadLockDetector();
_deadDetectThread.setDaemon(true);
_deadDetectThread.start();
} else {
_deadDetectThread = null;
}
System.gc();
// maxMemory is the upper limit the jvm can use, totalMemory the size of
// the current allocation pool, freeMemory the unused memory in the allocation pool
long freeMem = ((Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()) + Runtime.getRuntime().freeMemory()) / 1048576;
long totalMem = Runtime.getRuntime().maxMemory() / 1048576;
LOG.info("Started, free memory {} Mb of {} Mb", freeMem, totalMem);
Toolkit.getDefaultToolkit().beep();
LoginServerThread.getInstance().start();
final SelectorConfig sc = new SelectorConfig();
sc.MAX_READ_PER_PASS = mmo().getMaxReadPerPass();
sc.MAX_SEND_PER_PASS = mmo().getMaxSendPerPass();
sc.SLEEP_TIME = mmo().getSleepTime();
sc.HELPER_BUFFER_COUNT = mmo().getHelperBufferCount();
sc.TCP_NODELAY = mmo().isTcpNoDelay();
_gamePacketHandler = new L2GamePacketHandler();
_selectorThread = new SelectorThread<>(sc, _gamePacketHandler, _gamePacketHandler, _gamePacketHandler, new IPv4Filter());
InetAddress bindAddress = null;
if (!server().getHost().equals("*")) {
try {
bindAddress = InetAddress.getByName(server().getHost());
} catch (UnknownHostException ex) {
LOG.warn("Bind address is invalid, using all available IPs!", ex);
}
}
try {
_selectorThread.openServerSocket(bindAddress, server().getPort());
_selectorThread.start();
LOG.info("Now listening on {}:{}", server().getHost(), server().getPort());
} catch (IOException ex) {
LOG.error("Failed to open server socket!", ex);
System.exit(1);
}
if (server().enableUPnP()) {
startTimedSection("UPnP");
UPnPService.getInstance().load(server().getPort(), "L2J Game Server");
}
if (telnet().isEnabled()) {
new Status(telnet().getPort(), telnet().getPassword()).start();
} else {
LOG.info("Telnet server is currently disabled.");
}
LOG.info("Maximum numbers of connected players {}.", server().getMaxOnlineUsers());
LOG.info("Server {} loaded in {} seconds.", ServerNameDAO.getServer(hexId().getServerID()), MILLISECONDS.toSeconds(System.currentTimeMillis() - serverLoadStart));
}
public static void main(String[] args) throws Exception {
final String datapackRoot = Util.parseArg(args, DATAPACK, true);
if (datapackRoot != null) {
server().setProperty("DatapackRoot", datapackRoot);
}
final String scriptRoot = Util.parseArg(args, SCRIPT, true);
if (scriptRoot != null) {
server().setProperty("ScriptRoot", scriptRoot);
}
final String geodata = Util.parseArg(args, GEODATA, true);
if (geodata != null) {
geodata().setProperty("GeoDataPath", geodata);
}
gameServer = new GameServer();
}
public long getUsedMemoryMB() {
return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576;
}
public SelectorThread<L2GameClient> getSelectorThread() {
return _selectorThread;
}
public L2GamePacketHandler getL2GamePacketHandler() {
return _gamePacketHandler;
}
public DeadLockDetector getDeadLockDetectorThread() {
return _deadDetectThread;
}
public static long startTimedSection(String sectionName) {
final var sBuilder = new StringBuilder("=[ " + sectionName + " ]");
while (sBuilder.length() < 61) {
sBuilder.insert(0, "-");
}
LOG.info(sBuilder.toString());
return System.currentTimeMillis();
}
private static void endTimedSection(String sectionName, long startTime) {
LOG.info("[ {} loaded in {}ms ]", sectionName, System.currentTimeMillis() - startTime);
}
}
@@ -0,0 +1,169 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.instancemanager.DayNightSpawnManager;
import com.l2jserver.gameserver.model.actor.L2Character;
/**
* Game Time controller class.
* @author Forsaiken
*/
public final class GameTimeController extends Thread {
private static final Logger LOG = LoggerFactory.getLogger(GameTimeController.class);
public static final int TICKS_PER_SECOND = 10; // not able to change this without checking through code
public static final int MILLIS_IN_TICK = 1000 / TICKS_PER_SECOND;
public static final int IG_DAYS_PER_DAY = 6;
public static final int MILLIS_PER_IG_DAY = (3600000 * 24) / IG_DAYS_PER_DAY;
public static final int SECONDS_PER_IG_DAY = MILLIS_PER_IG_DAY / 1000;
public static final int MINUTES_PER_IG_DAY = SECONDS_PER_IG_DAY / 60;
public static final int TICKS_PER_IG_DAY = SECONDS_PER_IG_DAY * TICKS_PER_SECOND;
public static final int TICKS_PER_IG_HOUR = TICKS_PER_IG_DAY / 24;
public static final int TICKS_PER_IG_MINUTE = TICKS_PER_IG_HOUR / 60;
public static final int TICKS_SUN_STATE_CHANGE = TICKS_PER_IG_DAY / 4;
private static GameTimeController _instance;
private final Set<L2Character> _movingObjects = ConcurrentHashMap.newKeySet();
private final long _referenceTime;
private GameTimeController() {
super("GameTimeController");
super.setDaemon(true);
super.setPriority(MAX_PRIORITY);
final Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
_referenceTime = c.getTimeInMillis();
super.start();
}
public static void init() {
_instance = new GameTimeController();
}
public int getGameTime() {
return (getGameTicks() % TICKS_PER_IG_DAY) / TICKS_PER_IG_MINUTE;
}
public int getGameHour() {
return getGameTime() / 60;
}
public int getGameMinute() {
return getGameTime() % 60;
}
public boolean isNight() {
return getGameHour() < 6;
}
/**
* The true GameTime tick. Directly taken from current time. This represents the tick of the time.
* @return
*/
public int getGameTicks() {
return (int) ((System.currentTimeMillis() - _referenceTime) / MILLIS_IN_TICK);
}
/**
* Add a L2Character to movingObjects of GameTimeController.
* @param cha The L2Character to add to movingObjects of GameTimeController
*/
public void registerMovingObject(final L2Character cha) {
if (cha == null) {
return;
}
_movingObjects.add(cha);
}
/**
* Move all L2Characters contained in movingObjects of GameTimeController.<BR>
* <B><U> Concept</U> :</B><BR>
* All L2Character in movement are identified in <B>movingObjects</B> of GameTimeController.<BR>
* <B><U> Actions</U> :</B><BR>
* <ul>
* <li>Update the position of each L2Character</li>
* <li>If movement is finished, the L2Character is removed from movingObjects</li>
* <li>Create a task to update the _knownObject and _knowPlayers of each L2Character that finished its movement and of their already known L2Object then notify AI with EVT_ARRIVED</li>
* </ul>
*/
private void moveObjects() {
_movingObjects.removeIf(L2Character::updatePosition);
}
public void stopTimer() {
super.interrupt();
LOG.info("Stopping {}", getClass().getSimpleName());
}
@Override
public void run() {
LOG.debug("{}: Started.", getClass().getSimpleName());
long nextTickTime, sleepTime;
boolean isNight = isNight();
if (isNight) {
ThreadPoolManager.getInstance().executeAi(() -> DayNightSpawnManager.getInstance().notifyChangeMode());
}
while (true) {
nextTickTime = System.currentTimeMillis() + MILLIS_IN_TICK;
try {
moveObjects();
} catch (final Throwable e) {
LOG.warn("Unable to move objects!", e);
}
sleepTime = nextTickTime - System.currentTimeMillis();
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (final InterruptedException e) {
}
}
if (isNight() != isNight) {
isNight = !isNight;
ThreadPoolManager.getInstance().executeAi(() -> DayNightSpawnManager.getInstance().notifyChangeMode());
}
}
}
public static GameTimeController getInstance() {
return _instance;
}
}
@@ -0,0 +1,582 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.geodata;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.data.xml.impl.DoorData;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.interfaces.ILocational;
import com.l2jserver.gameserver.util.GeoUtils;
import com.l2jserver.gameserver.util.LinePointIterator;
import com.l2jserver.gameserver.util.LinePointIterator3D;
import com.l2jserver.geodriver.Cell;
import com.l2jserver.geodriver.GeoDriver;
/**
* Geodata.
* @author -Nemesiss-
* @author HorridoJoho
*/
public class GeoData {
private static final Logger LOG = LoggerFactory.getLogger(GeoData.class);
private static final String FILE_NAME_FORMAT = "%d_%d.l2j";
private static final int ELEVATED_SEE_OVER_DISTANCE = 2;
private static final int MAX_SEE_OVER_HEIGHT = 48;
private static final int SPAWN_Z_DELTA_LIMIT = 100;
private static final Map<String, Boolean> GEODATA_REGIONS = new HashMap<>();
private final GeoDriver _driver = new GeoDriver();
protected GeoData() {
loadGeodataRegions();
int loadedRegions = 0;
try {
for (int regionX = L2World.TILE_X_MIN; regionX <= L2World.TILE_X_MAX; regionX++) {
for (int regionY = L2World.TILE_Y_MIN; regionY <= L2World.TILE_Y_MAX; regionY++) {
final Path geoFilePath = geodata().getGeoDataPath().toPath().resolve(String.format(FILE_NAME_FORMAT, regionX, regionY));
final Boolean loadFile = GEODATA_REGIONS.get(regionX + "_" + regionY);
if (loadFile != null) {
if (loadFile) {
LOG.info("Loading {}...", geoFilePath.getFileName());
_driver.loadRegion(geoFilePath, regionX, regionY);
loadedRegions++;
}
} else if (geodata().tryLoadUnspecifiedRegions() && Files.exists(geoFilePath)) {
try {
LOG.info("Loading {}...", geoFilePath.getFileName());
_driver.loadRegion(geoFilePath, regionX, regionY);
loadedRegions++;
} catch (Exception ex) {
LOG.warn("Failed to load {}!", geoFilePath.getFileName(), ex);
}
}
}
}
} catch (Exception ex) {
LOG.error("Failed to load geodata!", ex);
System.exit(1);
}
LOG.info("Loaded {} regions.", loadedRegions);
}
private static void loadGeodataRegions() {
for (int regionX = L2World.TILE_X_MIN; regionX <= L2World.TILE_X_MAX; regionX++) {
for (int regionY = L2World.TILE_Y_MIN; regionY <= L2World.TILE_Y_MAX; regionY++) {
String key = regionX + "_" + regionY;
if (geodata().getIncludedRegions().contains(regionX + "_" + regionY)) {
GEODATA_REGIONS.put(key, true);
} else if (geodata().getExcludedRegions().contains(regionX + "_" + regionY)) {
GEODATA_REGIONS.put(key, false);
}
}
}
}
public boolean hasGeoPos(int geoX, int geoY) {
return _driver.hasGeoPos(geoX, geoY);
}
public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) {
return _driver.checkNearestNswe(geoX, geoY, worldZ, nswe);
}
public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) {
boolean can = true;
if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) {
// can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.NORTH);
can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH);
}
if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) {
// can = canEnterNeighbors(prevX, prevY - 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.NORTH);
can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_NORTH);
}
if (can && ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST)) {
// can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.EAST) && canEnterNeighbors(prevX + 1, prevY, prevGeoZ, Direction.SOUTH);
can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_SOUTH);
}
if (can && ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST)) {
// can = canEnterNeighbors(prevX, prevY + 1, prevGeoZ, Direction.WEST) && canEnterNeighbors(prevX - 1, prevY, prevGeoZ, Direction.SOUTH);
can = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH);
}
return can && checkNearestNswe(geoX, geoY, worldZ, nswe);
}
public int getNearestZ(int geoX, int geoY, int worldZ) {
return _driver.getNearestZ(geoX, geoY, worldZ);
}
public int getNextLowerZ(int geoX, int geoY, int worldZ) {
return _driver.getNextLowerZ(geoX, geoY, worldZ);
}
public int getNextHigherZ(int geoX, int geoY, int worldZ) {
return _driver.getNextHigherZ(geoX, geoY, worldZ);
}
public int getGeoX(int worldX) {
return _driver.getGeoX(worldX);
}
public int getGeoY(int worldY) {
return _driver.getGeoY(worldY);
}
public int getWorldX(int geoX) {
return _driver.getWorldX(geoX);
}
public int getWorldY(int geoY) {
return _driver.getWorldY(geoY);
}
// ///////////////////
// L2J METHODS
/**
* Gets the height.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the height
*/
public int getHeight(int x, int y, int z) {
return getNearestZ(getGeoX(x), getGeoY(y), z);
}
/**
* Gets the spawn height.
* @param x the x coordinate
* @param y the y coordinate
* @param z the the z coordinate
* @return the spawn height
*/
public int getSpawnHeight(int x, int y, int z) {
final int geoX = getGeoX(x);
final int geoY = getGeoY(y);
if (!hasGeoPos(geoX, geoY)) {
return z;
}
int nextLowerZ = getNextLowerZ(geoX, geoY, z + 20);
return Math.abs(nextLowerZ - z) <= SPAWN_Z_DELTA_LIMIT ? nextLowerZ : z;
}
/**
* Gets the spawn height.
* @param location the location
* @return the spawn height
*/
public int getSpawnHeight(Location location) {
return getSpawnHeight(location.getX(), location.getY(), location.getZ());
}
/**
* Can see target. Doors as target always return true. Checks doors between.
* @param cha the character
* @param target the target
* @return {@code true} if the character can see the target (LOS), {@code false} otherwise
*/
public boolean canSeeTarget(L2Object cha, L2Object target) {
if (target == null) {
return false;
}
if (target.isDoor()) {
return true;
}
return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), target.getX(), target.getY(), target.getZ(), target.getInstanceId());
}
/**
* Can see target. Checks doors between.
* @param cha the character
* @param worldPosition the world position
* @return {@code true} if the character can see the target at the given world position, {@code false} otherwise
*/
public boolean canSeeTarget(L2Object cha, ILocational worldPosition) {
return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
}
/**
* Can see target. Checks doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param instanceId
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @param tInstanceId the target's instanceId
* @return
*/
public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) {
if ((instanceId != tInstanceId)) {
return false;
}
return canSeeTarget(x, y, z, instanceId, tx, ty, tz);
}
/**
* Can see target. Checks doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param instanceId
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise
*/
public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz) {
if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) {
return false;
}
return canSeeTarget(x, y, z, tx, ty, tz);
}
private int getLosGeoZ(int prevX, int prevY, int prevGeoZ, int curX, int curY, int nswe) {
if ((((nswe & Cell.NSWE_NORTH) != 0) && ((nswe & Cell.NSWE_SOUTH) != 0)) || (((nswe & Cell.NSWE_WEST) != 0) && ((nswe & Cell.NSWE_EAST) != 0))) {
throw new RuntimeException("Multiple directions!");
}
if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) {
return getNearestZ(curX, curY, prevGeoZ);
}
return getNextHigherZ(curX, curY, prevGeoZ);
}
/**
* Can see target. Does not check doors between.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise
*/
public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) {
int geoX = getGeoX(x);
int geoY = getGeoY(y);
int tGeoX = getGeoX(tx);
int tGeoY = getGeoY(ty);
z = getNearestZ(geoX, geoY, z);
tz = getNearestZ(tGeoX, tGeoY, tz);
// fast path
if ((geoX == tGeoX) && (geoY == tGeoY)) {
if (hasGeoPos(tGeoX, tGeoY)) {
return z == tz;
}
return true;
}
if (tz > z) {
int tmp = tx;
tx = x;
x = tmp;
tmp = ty;
ty = y;
y = tmp;
tmp = tz;
tz = z;
z = tmp;
tmp = tGeoX;
tGeoX = geoX;
geoX = tmp;
tmp = tGeoY;
tGeoY = geoY;
geoY = tmp;
}
LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz);
// first point is guaranteed to be available, skip it, we can always see our own position
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
int prevZ = pointIter.z();
int prevGeoZ = prevZ;
int ptIndex = 0;
while (pointIter.next()) {
int curX = pointIter.x();
int curY = pointIter.y();
if ((curX == prevX) && (curY == prevY)) {
continue;
}
int beeCurZ = pointIter.z();
int curGeoZ = prevGeoZ;
// check if the position has geodata
if (hasGeoPos(curX, curY)) {
int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY);
curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe);
int maxHeight;
if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) {
maxHeight = z + MAX_SEE_OVER_HEIGHT;
} else {
maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT;
}
boolean canSeeThrough = false;
if (curGeoZ <= maxHeight) {
if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) {
int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_EAST);
int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_NORTH);
canSeeThrough = (northGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ));
} else if ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST) {
int northGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY - 1, Cell.NSWE_WEST);
int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_NORTH);
canSeeThrough = (northGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (northGeoZ <= getNearestZ(prevX, prevY - 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ));
} else if ((nswe & Cell.NSWE_SOUTH_EAST) == Cell.NSWE_SOUTH_EAST) {
int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_EAST);
int eastGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX + 1, prevY, Cell.NSWE_SOUTH);
canSeeThrough = (southGeoZ <= maxHeight) && (eastGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (eastGeoZ <= getNearestZ(prevX + 1, prevY, beeCurZ));
} else if ((nswe & Cell.NSWE_SOUTH_WEST) == Cell.NSWE_SOUTH_WEST) {
int southGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX, prevY + 1, Cell.NSWE_WEST);
int westGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, prevX - 1, prevY, Cell.NSWE_SOUTH);
canSeeThrough = (southGeoZ <= maxHeight) && (westGeoZ <= maxHeight) && (southGeoZ <= getNearestZ(prevX, prevY + 1, beeCurZ)) && (westGeoZ <= getNearestZ(prevX - 1, prevY, beeCurZ));
} else {
canSeeThrough = true;
}
}
if (!canSeeThrough) {
return false;
}
}
prevX = curX;
prevY = curY;
prevGeoZ = curGeoZ;
++ptIndex;
}
return true;
}
/**
* Verifies if the is a path between origin's location and destination, if not returns the closest location.
* @param origin the origin
* @param destination the destination
* @return the destination if there is a path or the closes location
*/
public Location moveCheck(ILocational origin, ILocational destination) {
return moveCheck(origin.getX(), origin.getY(), origin.getZ(), destination.getX(), destination.getY(), destination.getZ(), origin.getInstanceId());
}
/**
* Move check.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param tx the target's x coordinate
* @param ty the target's y coordinate
* @param tz the target's z coordinate
* @param instanceId the instance id
* @return the last Location (x,y,z) where player can walk - just before wall
*/
public Location moveCheck(int x, int y, int z, int tx, int ty, int tz, int instanceId) {
int geoX = getGeoX(x);
int geoY = getGeoY(y);
z = getNearestZ(geoX, geoY, z);
int tGeoX = getGeoX(tx);
int tGeoY = getGeoY(ty);
tz = getNearestZ(tGeoX, tGeoY, tz);
if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false)) {
return new Location(x, y, getHeight(x, y, z));
}
LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
int prevZ = z;
while (pointIter.next()) {
int curX = pointIter.x();
int curY = pointIter.y();
int curZ = getNearestZ(curX, curY, prevZ);
if (hasGeoPos(prevX, prevY)) {
int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY);
if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) {
// can't move, return previous location
return new Location(getWorldX(prevX), getWorldY(prevY), prevZ);
}
}
prevX = curX;
prevY = curY;
prevZ = curZ;
}
if (hasGeoPos(prevX, prevY) && (prevZ != tz)) {
// different floors, return start location
return new Location(x, y, z);
}
return new Location(tx, ty, tz);
}
/**
* Checks if its possible to move from one location to another.
* @param fromX the X coordinate to start checking from
* @param fromY the Y coordinate to start checking from
* @param fromZ the Z coordinate to start checking from
* @param toX the X coordinate to end checking at
* @param toY the Y coordinate to end checking at
* @param toZ the Z coordinate to end checking at
* @param instanceId the instance ID
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int instanceId) {
int geoX = getGeoX(fromX);
int geoY = getGeoY(fromY);
fromZ = getNearestZ(geoX, geoY, fromZ);
int tGeoX = getGeoX(toX);
int tGeoY = getGeoY(toY);
toZ = getNearestZ(tGeoX, tGeoY, toZ);
if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId, false)) {
return false;
}
LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevX = pointIter.x();
int prevY = pointIter.y();
int prevZ = fromZ;
while (pointIter.next()) {
int curX = pointIter.x();
int curY = pointIter.y();
int curZ = getNearestZ(curX, curY, prevZ);
if (hasGeoPos(prevX, prevY)) {
int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY);
if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) {
return false;
}
}
prevX = curX;
prevY = curY;
prevZ = curZ;
}
// different floors
return !hasGeoPos(prevX, prevY) || (prevZ == toZ);
}
public int traceTerrainZ(int x, int y, int z, int tx, int ty) {
int geoX = getGeoX(x);
int geoY = getGeoY(y);
z = getNearestZ(geoX, geoY, z);
int tGeoX = getGeoX(tx);
int tGeoY = getGeoY(ty);
LinePointIterator pointIter = new LinePointIterator(geoX, geoY, tGeoX, tGeoY);
// first point is guaranteed to be available
pointIter.next();
int prevZ = z;
while (pointIter.next()) {
int curX = pointIter.x();
int curY = pointIter.y();
int curZ = getNearestZ(curX, curY, prevZ);
prevZ = curZ;
}
return prevZ;
}
/**
* Checks if its possible to move from one location to another.
* @param from the {@code ILocational} to start checking from
* @param toX the X coordinate to end checking at
* @param toY the Y coordinate to end checking at
* @param toZ the Z coordinate to end checking at
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(ILocational from, int toX, int toY, int toZ) {
return canMove(from.getX(), from.getY(), from.getZ(), toX, toY, toZ, from.getInstanceId());
}
/**
* Checks if its possible to move from one location to another.
* @param from the {@code ILocational} to start checking from
* @param to the {@code ILocational} to end checking at
* @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise
*/
public boolean canMove(ILocational from, ILocational to) {
return canMove(from, to.getX(), to.getY(), to.getZ());
}
/**
* Checks the specified position for available geodata.
* @param x the X coordinate
* @param y the Y coordinate
* @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise
*/
public boolean hasGeo(int x, int y) {
return hasGeoPos(getGeoX(x), getGeoY(y));
}
public static GeoData getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
protected static final GeoData INSTANCE = new GeoData();
}
}
@@ -0,0 +1,41 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
/**
* Interface for managers of list of instances.
* @author fordfrog
*/
public interface InstanceListManager {
/**
* Loads instances with their data from persistent format.<br>
* This method has no side effect as calling methods of another instance manager.
*/
void loadInstances();
/**
* For each loaded instance, updates references to related instances.
*/
void updateReferences();
/**
* Activates instances so their setup is performed.
*/
void activateInstances();
}
@@ -0,0 +1,94 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.general;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jserver.gameserver.enums.ItemLocation;
import com.l2jserver.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
public final class ItemsAutoDestroy {
private final Map<Integer, L2ItemInstance> _items = new ConcurrentHashMap<>();
protected ItemsAutoDestroy() {
ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(this::removeItems, 5000, 5000);
}
public static ItemsAutoDestroy getInstance() {
return SingletonHolder._instance;
}
public synchronized void addItem(L2ItemInstance item) {
item.setDropTime(System.currentTimeMillis());
_items.put(item.getObjectId(), item);
}
public synchronized void removeItems() {
final long curtime = System.currentTimeMillis();
for (L2ItemInstance item : _items.values()) {
if (item == null) {
continue;
}
if ((item.getDropTime() == 0) || (item.getItemLocation() != ItemLocation.VOID)) {
_items.remove(item.getObjectId());
} else {
if (item.getItem().getAutoDestroyTime() > 0) {
if ((curtime - item.getDropTime()) > item.getItem().getAutoDestroyTime()) {
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (general().saveDroppedItem()) {
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
} else if (item.getItem().hasExImmediateEffect()) {
if ((curtime - item.getDropTime()) > general().getAutoDestroyHerbTime()) {
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (general().saveDroppedItem()) {
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
} else {
final long sleep = ((general().getAutoDestroyDroppedItemAfter() == 0) ? 3600000 : general().getAutoDestroyDroppedItemAfter() * 1000);
if ((curtime - item.getDropTime()) > sleep) {
L2World.getInstance().removeVisibleObject(item, item.getWorldRegion());
L2World.getInstance().removeObject(item);
_items.remove(item.getObjectId());
if (general().saveDroppedItem()) {
ItemsOnGroundManager.getInstance().removeObject(item);
}
}
}
}
}
}
private static class SingletonHolder {
protected static final ItemsAutoDestroy _instance = new ItemsAutoDestroy();
}
}
@@ -0,0 +1,705 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.general;
import static com.l2jserver.gameserver.config.Configuration.hexId;
import static com.l2jserver.gameserver.config.Configuration.ip;
import static com.l2jserver.gameserver.config.Configuration.server;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.commons.network.BaseSendablePacket;
import com.l2jserver.commons.security.crypt.NewCrypt;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.commons.util.Util;
import com.l2jserver.gameserver.config.Configuration;
import com.l2jserver.gameserver.config.HexIdConfiguration;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.network.L2GameClient;
import com.l2jserver.gameserver.network.L2GameClient.GameClientState;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.gameserverpackets.AuthRequest;
import com.l2jserver.gameserver.network.gameserverpackets.BlowFishKey;
import com.l2jserver.gameserver.network.gameserverpackets.ChangeAccessLevel;
import com.l2jserver.gameserver.network.gameserverpackets.ChangePassword;
import com.l2jserver.gameserver.network.gameserverpackets.PlayerAuthRequest;
import com.l2jserver.gameserver.network.gameserverpackets.PlayerInGame;
import com.l2jserver.gameserver.network.gameserverpackets.PlayerLogout;
import com.l2jserver.gameserver.network.gameserverpackets.PlayerTracert;
import com.l2jserver.gameserver.network.gameserverpackets.ReplyCharacters;
import com.l2jserver.gameserver.network.gameserverpackets.SendMail;
import com.l2jserver.gameserver.network.gameserverpackets.ServerStatus;
import com.l2jserver.gameserver.network.gameserverpackets.TempBan;
import com.l2jserver.gameserver.network.loginserverpackets.AuthResponse;
import com.l2jserver.gameserver.network.loginserverpackets.ChangePasswordResponse;
import com.l2jserver.gameserver.network.loginserverpackets.InitLS;
import com.l2jserver.gameserver.network.loginserverpackets.KickPlayer;
import com.l2jserver.gameserver.network.loginserverpackets.LoginServerFail;
import com.l2jserver.gameserver.network.loginserverpackets.PlayerAuthResponse;
import com.l2jserver.gameserver.network.loginserverpackets.RequestCharacters;
import com.l2jserver.gameserver.network.serverpackets.CharSelectionInfo;
import com.l2jserver.gameserver.network.serverpackets.LoginFail;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
public class LoginServerThread extends Thread {
protected static final Logger LOG = LoggerFactory.getLogger(LoginServerThread.class);
protected static final Logger LOG_ACCOUNTING = LoggerFactory.getLogger("accounting");
private static final int TEENAGER = 15;
private static final int ADULT = 18;
private final String _hostname;
private final int _port;
private final int _gamePort;
private Socket _loginSocket;
private OutputStream _out;
/**
* The BlowFish engine used to encrypt packets<br>
* It is first initialized with a unified key:<br>
* "_;v.]05-31!|+-%xT!^[$\00"<br>
* <br>
* and then after handshake, with a new key sent by<br>
* login server during the handshake. This new key is stored<br>
* in blowfishKey
*/
private NewCrypt _blowfish;
private final byte[] _hexID;
private final boolean _acceptAlternate;
private final int _requestID;
private final boolean _reserveHost;
private int _maxPlayer;
private final List<WaitingClient> _waitingClients;
private final Map<String, L2GameClient> _accountsInGameServer = new ConcurrentHashMap<>();
private int _status;
private String _serverName;
private final List<String> _subnets;
private final List<String> _hosts;
protected LoginServerThread() {
super("LoginServerThread");
_port = server().getLoginPort();
_gamePort = server().getPort();
_hostname = server().getLoginHost();
if (hexId().getHexID() == null) {
_hexID = Util.generateHex(16);
_requestID = server().getRequestServerId();
hexId().setProperty("ServerID", String.valueOf(_requestID));
} else {
_hexID = hexId().getHexID().toByteArray();
_requestID = hexId().getServerID();
}
_acceptAlternate = server().acceptAlternateId();
_reserveHost = server().reserveHostOnLogin();
_subnets = ip().getSubnets();
_hosts = ip().getHosts();
_waitingClients = new CopyOnWriteArrayList<>();
_maxPlayer = server().getMaxOnlineUsers();
}
@Override
public void run() {
while (!isInterrupted()) {
int lengthHi;
int lengthLo;
int length;
boolean checksumOk;
try {
// Connection
LOG.info("Connecting to login server on {}:{}", _hostname, _port);
_loginSocket = new Socket(_hostname, _port);
InputStream in = _loginSocket.getInputStream();
_out = new BufferedOutputStream(_loginSocket.getOutputStream());
// init Blowfish
byte[] blowfishKey = Util.generateHex(40);
// Protect the new blowfish key what cannot begin with zero
if (blowfishKey[0] == 0) {
blowfishKey[0] = (byte) Rnd.get(32, 64);
}
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
while (!isInterrupted()) {
lengthLo = in.read();
lengthHi = in.read();
length = (lengthHi * 256) + lengthLo;
if (lengthHi < 0) {
LOG.info("LoginServerThread: Login terminated the connection.");
break;
}
byte[] incoming = new byte[length - 2];
int receivedBytes = 0;
int newBytes = 0;
int left = length - 2;
while ((newBytes != -1) && (receivedBytes < (length - 2))) {
newBytes = in.read(incoming, receivedBytes, left);
receivedBytes = receivedBytes + newBytes;
left -= newBytes;
}
if (receivedBytes != (length - 2)) {
LOG.warn("Incomplete Packet is sent to the server, closing connection.(LS)");
break;
}
// decrypt if we have a key
_blowfish.decrypt(incoming, 0, incoming.length);
checksumOk = NewCrypt.verifyChecksum(incoming);
if (!checksumOk) {
LOG.warn("Incorrect packet checksum, ignoring packet (LS)");
break;
}
int packetType = incoming[0] & 0xff;
// send the blowfish key through the rsa encryption
// now, only accept packet with the new encryption
// login will close the connection here
switch (packetType) {
case 0x00 -> {
InitLS init = new InitLS(incoming);
RSAPublicKey publicKey;
try {
KeyFactory kfac = KeyFactory.getInstance("RSA");
BigInteger modulus = new BigInteger(init.getRSAKey());
RSAPublicKeySpec kspec1 = new RSAPublicKeySpec(modulus, RSAKeyGenParameterSpec.F4);
publicKey = (RSAPublicKey) kfac.generatePublic(kspec1);
} catch (GeneralSecurityException e) {
LOG.warn("Trouble while init the public key send by login");
break;
}
sendPacket(new BlowFishKey(blowfishKey, publicKey));
_blowfish = new NewCrypt(blowfishKey);
sendPacket(new AuthRequest(_requestID, _acceptAlternate, _hexID, _gamePort, _reserveHost, _maxPlayer, _subnets, _hosts));
}
case 0x01 -> {
LoginServerFail lsf = new LoginServerFail(incoming);
LOG.info("Damn! Registration Failed: {}", lsf.getReasonString());
}
case 0x02 -> {
AuthResponse aresp = new AuthResponse(incoming);
int serverID = aresp.getServerId();
_serverName = aresp.getServerName();
saveHexid(serverID, hexToString(_hexID));
LOG.info("Registered on login as Server {}: {}", serverID, _serverName);
ServerStatus st = new ServerStatus();
if (general().getServerListBrackets()) {
st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.ON);
} else {
st.addAttribute(ServerStatus.SERVER_LIST_SQUARE_BRACKET, ServerStatus.OFF);
}
st.addAttribute(ServerStatus.SERVER_TYPE, general().getServerListType());
if (general().serverGMOnly()) {
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
} else {
st.addAttribute(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO);
}
if (general().getServerListAge() == TEENAGER) {
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_15);
} else if (general().getServerListAge() == ADULT) {
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_18);
} else {
st.addAttribute(ServerStatus.SERVER_AGE, ServerStatus.SERVER_AGE_ALL);
}
sendPacket(st);
if (L2World.getInstance().getAllPlayersCount() > 0) {
final List<String> playerList = new ArrayList<>();
for (L2PcInstance player : L2World.getInstance().getPlayers()) {
playerList.add(player.getAccountName());
}
sendPacket(new PlayerInGame(playerList));
}
}
case 0x03 -> {
PlayerAuthResponse par = new PlayerAuthResponse(incoming);
String account = par.getAccount();
WaitingClient wcToRemove = null;
synchronized (_waitingClients) {
for (WaitingClient wc : _waitingClients) {
if (wc.account.equals(account)) {
wcToRemove = wc;
}
}
}
if (wcToRemove != null) {
if (par.isAuthed()) {
PlayerInGame pig = new PlayerInGame(par.getAccount());
sendPacket(pig);
wcToRemove.gameClient.setState(GameClientState.AUTHED);
wcToRemove.gameClient.setSessionId(wcToRemove.session);
CharSelectionInfo cl = new CharSelectionInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1);
wcToRemove.gameClient.getConnection().sendPacket(cl);
wcToRemove.gameClient.setCharSelection(cl.getCharInfo());
} else {
LOG.warn("Session key is not correct. Closing connection for account {}.", wcToRemove.account);
// wcToRemove.gameClient.getConnection().sendPacket(new LoginFail(LoginFail.SYSTEM_ERROR_LOGIN_LATER));
wcToRemove.gameClient.close(new LoginFail(LoginFail.SYSTEM_ERROR_LOGIN_LATER));
_accountsInGameServer.remove(wcToRemove.account);
}
_waitingClients.remove(wcToRemove);
}
}
case 0x04 -> {
KickPlayer kp = new KickPlayer(incoming);
doKickPlayer(kp.getAccount());
}
case 0x05 -> {
RequestCharacters rc = new RequestCharacters(incoming);
getCharsOnServer(rc.getAccount());
}
case 0x06 -> new ChangePasswordResponse(incoming);
}
}
} catch (UnknownHostException e) {
LOG.warn("Unknown host!", e);
} catch (SocketException e) {
LOG.warn("LoginServer not avaible, trying to reconnect...");
} catch (IOException e) {
LOG.warn("Disconnected from Login, Trying to reconnect!", e);
} finally {
try {
_loginSocket.close();
if (isInterrupted()) {
return;
}
} catch (Exception e) {
}
}
try {
Thread.sleep(5000); // 5 seconds tempo.
} catch (InterruptedException e) {
return; // never swallow an interrupt!
}
}
}
/**
* Adds the waiting client and send request.
* @param acc the account
* @param client the game client
* @param key the session key
*/
public void addWaitingClientAndSendRequest(String acc, L2GameClient client, SessionKey key) {
WaitingClient wc = new WaitingClient(acc, client, key);
synchronized (_waitingClients) {
_waitingClients.add(wc);
}
PlayerAuthRequest par = new PlayerAuthRequest(acc, key);
try {
sendPacket(par);
} catch (IOException e) {
LOG.warn("Error while sending player auth request!");
}
}
/**
* Removes the waiting client.
* @param client the client
*/
public void removeWaitingClient(L2GameClient client) {
WaitingClient toRemove = null;
synchronized (_waitingClients) {
for (WaitingClient c : _waitingClients) {
if (c.gameClient == client) {
toRemove = c;
}
}
if (toRemove != null) {
_waitingClients.remove(toRemove);
}
}
}
/**
* Send logout for the given account.
* @param account the account
*/
public void sendLogout(String account) {
if (account == null) {
return;
}
PlayerLogout pl = new PlayerLogout(account);
try {
sendPacket(pl);
} catch (IOException e) {
LOG.warn("Error while sending logout packet to login!");
} finally {
_accountsInGameServer.remove(account);
}
}
/**
* Adds the game server login.
* @param account the account
* @param client the client
* @return {@code true} if account was not already logged in, {@code false} otherwise
*/
public boolean addGameServerLogin(String account, L2GameClient client) {
return _accountsInGameServer.putIfAbsent(account, client) == null;
}
/**
* Send access level.
* @param account the account
* @param level the access level
*/
public void sendAccessLevel(String account, int level) {
ChangeAccessLevel cal = new ChangeAccessLevel(account, level);
try {
sendPacket(cal);
} catch (IOException e) {
}
}
/**
* Send client tracert.
* @param account the account
* @param address the address
*/
public void sendClientTracert(String account, String[] address) {
PlayerTracert ptc = new PlayerTracert(account, address[0], address[1], address[2], address[3], address[4]);
try {
sendPacket(ptc);
} catch (IOException e) {
}
}
/**
* Send mail.
* @param account the account
* @param mailId the mail id
* @param args the args
*/
public void sendMail(String account, String mailId, String... args) {
SendMail sem = new SendMail(account, mailId, args);
try {
sendPacket(sem);
} catch (IOException e) {
}
}
/**
* Send temp ban.
* @param account the account
* @param ip the ip
* @param time the time
*/
public void sendTempBan(String account, String ip, long time) {
TempBan tbn = new TempBan(account, ip, time);
try {
sendPacket(tbn);
} catch (IOException e) {
}
}
/**
* Hex to string.
* @param hex the hex value
* @return the hex value as string
*/
private String hexToString(byte[] hex) {
return new BigInteger(hex).toString(16);
}
/**
* Kick player for the given account.
* @param account the account
*/
public void doKickPlayer(String account) {
L2GameClient client = _accountsInGameServer.get(account);
if (client != null) {
LOG_ACCOUNTING.warn("Kicked by login: {}", client);
client.setAdditionalClosePacket(SystemMessage.getSystemMessage(SystemMessageId.ANOTHER_LOGIN_WITH_ACCOUNT));
client.closeNow();
}
}
/**
* Gets the chars on server.
* @param account the account
*/
private void getCharsOnServer(String account) {
int chars = 0;
List<Long> charToDel = new ArrayList<>();
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement("SELECT deletetime FROM characters WHERE account_name=?")) {
ps.setString(1, account);
try (var rs = ps.executeQuery()) {
while (rs.next()) {
chars++;
long delTime = rs.getLong("deletetime");
if (delTime != 0) {
charToDel.add(delTime);
}
}
}
} catch (SQLException e) {
LOG.warn("Exception: getCharsOnServer!", e);
}
ReplyCharacters rec = new ReplyCharacters(account, chars, charToDel);
try {
sendPacket(rec);
} catch (IOException e) {
}
}
/**
* Send packet.
* @param sl the sendable packet
* @throws IOException Signals that an I/O exception has occurred.
*/
private void sendPacket(BaseSendablePacket sl) throws IOException {
byte[] data = sl.getContent();
NewCrypt.appendChecksum(data);
_blowfish.crypt(data, 0, data.length);
int len = data.length + 2;
synchronized (_out) // avoids tow threads writing in the mean time
{
_out.write(len & 0xff);
_out.write((len >> 8) & 0xff);
_out.write(data);
_out.flush();
}
}
/**
* Sets the max player.
* @param maxPlayer The maxPlayer to set.
*/
public void setMaxPlayer(int maxPlayer) {
sendServerStatus(ServerStatus.MAX_PLAYERS, maxPlayer);
_maxPlayer = maxPlayer;
}
/**
* Gets the max player.
* @return Returns the maxPlayer.
*/
public int getMaxPlayer() {
return _maxPlayer;
}
/**
* Send server status.
* @param id the id
* @param value the value
*/
public void sendServerStatus(int id, int value) {
ServerStatus ss = new ServerStatus();
ss.addAttribute(id, value);
try {
sendPacket(ss);
} catch (IOException e) {
}
}
/**
* Send Server Type Config to LS.
*/
public void sendServerType() {
ServerStatus ss = new ServerStatus();
ss.addAttribute(ServerStatus.SERVER_TYPE, general().getServerListType());
try {
sendPacket(ss);
} catch (IOException e) {
}
}
/**
* Send change password.
* @param accountName the account name
* @param charName the char name
* @param oldpass the old pass
* @param newpass the new pass
*/
public void sendChangePassword(String accountName, String charName, String oldpass, String newpass) {
ChangePassword cp = new ChangePassword(accountName, charName, oldpass, newpass);
try {
sendPacket(cp);
} catch (IOException e) {
}
}
/**
* Gets the status string.
* @return the status string
*/
public String getStatusString() {
return ServerStatus.STATUS_STRING[_status];
}
/**
* Gets the server name.
* @return the server name.
*/
public String getServerName() {
return _serverName;
}
/**
* Sets the server status.
* @param status the new server status
*/
public void setServerStatus(int status) {
switch (status) {
case ServerStatus.STATUS_AUTO -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_AUTO);
_status = status;
}
case ServerStatus.STATUS_DOWN -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_DOWN);
_status = status;
}
case ServerStatus.STATUS_FULL -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_FULL);
_status = status;
}
case ServerStatus.STATUS_GM_ONLY -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GM_ONLY);
_status = status;
}
case ServerStatus.STATUS_GOOD -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_GOOD);
_status = status;
}
case ServerStatus.STATUS_NORMAL -> {
sendServerStatus(ServerStatus.SERVER_LIST_STATUS, ServerStatus.STATUS_NORMAL);
_status = status;
}
default -> throw new IllegalArgumentException("Status does not exists:" + status);
}
}
public L2GameClient getClient(String name) {
return name != null ? _accountsInGameServer.get(name) : null;
}
public static class SessionKey {
public int playOkID1;
public int playOkID2;
public int loginOkID1;
public int loginOkID2;
/**
* Instantiates a new session key.
* @param loginOK1 the login o k1
* @param loginOK2 the login o k2
* @param playOK1 the play o k1
* @param playOK2 the play o k2
*/
public SessionKey(int loginOK1, int loginOK2, int playOK1, int playOK2) {
playOkID1 = playOK1;
playOkID2 = playOK2;
loginOkID1 = loginOK1;
loginOkID2 = loginOK2;
}
@Override
public String toString() {
return "PlayOk: " + playOkID1 + " " + playOkID2 + " LoginOk:" + loginOkID1 + " " + loginOkID2;
}
}
private static class WaitingClient {
public String account;
public L2GameClient gameClient;
public SessionKey session;
/**
* Instantiates a new waiting client.
* @param acc the acc
* @param client the client
* @param key the key
*/
public WaitingClient(String acc, L2GameClient client, SessionKey key) {
account = acc;
gameClient = client;
session = key;
}
}
/**
* Save hexadecimal ID of the server in the L2Properties file.
* @param serverId the ID of the server whose hexId to save
* @param hexId the hexadecimal ID to store
*/
public static void saveHexid(int serverId, String hexId) {
final var hexIdFilePath = Configuration.getCustomOrDefaultPath(HexIdConfiguration.FILENAME);
hexId().setProperty(HexIdConfiguration.SERVERID_KEY, String.valueOf(serverId));
hexId().setProperty(HexIdConfiguration.HEXID_KEY, hexId);
try {
Files.createDirectories(hexIdFilePath.getParent());
try (var out = Files.newOutputStream(hexIdFilePath, CREATE, TRUNCATE_EXISTING)) {
hexId().store(out, "the hexID to auth into login");
LOG.info("Saved {}.", hexIdFilePath);
}
} catch (Exception ex) {
LOG.warn("Failed to save {}.", hexIdFilePath, ex);
}
}
/**
* Gets the single instance of LoginServerThread.
* @return single instance of LoginServerThread
*/
public static LoginServerThread getInstance() {
return SingletonHolder._instance;
}
private static class SingletonHolder {
protected static final LoginServerThread _instance = new LoginServerThread();
}
}
@@ -0,0 +1,125 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.data.xml.impl.NpcData;
import com.l2jserver.gameserver.idfactory.IdFactory;
import com.l2jserver.gameserver.model.actor.L2Npc;
public class MonsterRace {
protected static final Logger LOG = LoggerFactory.getLogger(MonsterRace.class);
private final L2Npc[] _monsters;
private int[][] _speeds;
private final int[] _first, _second;
protected MonsterRace() {
_monsters = new L2Npc[8];
_speeds = new int[8][20];
_first = new int[2];
_second = new int[2];
}
public static MonsterRace getInstance() {
return SingletonHolder._instance;
}
public void newRace() {
int random;
for (int i = 0; i < 8; i++) {
int id = 31003;
random = Rnd.get(24);
while (true) {
for (int j = i - 1; j >= 0; j--) {
if (_monsters[j].getTemplate().getId() == (id + random)) {
random = Rnd.get(24);
}
}
break;
}
try {
final var objectId = IdFactory.getInstance().getNextId();
final var template = NpcData.getInstance().getTemplate(id + random);
final var constructor = Class.forName("com.l2jserver.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0];
_monsters[i] = (L2Npc) constructor.newInstance(objectId, template);
} catch (Exception e) {
LOG.warn("Unable to create monster!", e);
}
}
newSpeeds();
}
public void newSpeeds() {
_speeds = new int[8][20];
int total;
_first[1] = 0;
_second[1] = 0;
for (int i = 0; i < 8; i++) {
total = 0;
for (int j = 0; j < 20; j++) {
if (j == 19) {
_speeds[i][j] = 100;
} else {
_speeds[i][j] = Rnd.get(60) + 65;
}
total += _speeds[i][j];
}
if (total >= _first[1]) {
_second[0] = _first[0];
_second[1] = _first[1];
_first[0] = 8 - i;
_first[1] = total;
} else if (total >= _second[1]) {
_second[0] = 8 - i;
_second[1] = total;
}
}
}
/**
* @return Returns the monsters.
*/
public L2Npc[] getMonsters() {
return _monsters;
}
/**
* @return Returns the speeds.
*/
public int[][] getSpeeds() {
return _speeds;
}
public int getFirstPlace() {
return _first[0];
}
public int getSecondPlace() {
return _second[0];
}
private static class SingletonHolder {
protected static final MonsterRace _instance = new MonsterRace();
}
}
@@ -0,0 +1,664 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.character;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.data.xml.impl.RecipeData;
import com.l2jserver.gameserver.datatables.ItemTable;
import com.l2jserver.gameserver.enums.StatType;
import com.l2jserver.gameserver.model.L2ManufactureItem;
import com.l2jserver.gameserver.model.L2RecipeInstance;
import com.l2jserver.gameserver.model.L2RecipeList;
import com.l2jserver.gameserver.model.L2RecipeStatInstance;
import com.l2jserver.gameserver.model.TempItem;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.itemcontainer.Inventory;
import com.l2jserver.gameserver.model.items.L2Item;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
import com.l2jserver.gameserver.model.skills.CommonSkill;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
import com.l2jserver.gameserver.network.serverpackets.ItemList;
import com.l2jserver.gameserver.network.serverpackets.MagicSkillUse;
import com.l2jserver.gameserver.network.serverpackets.RecipeBookItemList;
import com.l2jserver.gameserver.network.serverpackets.RecipeItemMakeInfo;
import com.l2jserver.gameserver.network.serverpackets.RecipeShopItemInfo;
import com.l2jserver.gameserver.network.serverpackets.SetupGauge;
import com.l2jserver.gameserver.network.serverpackets.StatusUpdate;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.util.Util;
public class RecipeController {
protected static final Map<Integer, RecipeItemMaker> _activeMakers = new ConcurrentHashMap<>();
protected RecipeController() {
// Prevent external initialization.
}
public void requestBookOpen(L2PcInstance player, boolean isDwarvenCraft) {
// Check if player is trying to alter recipe book while engaged in manufacturing.
if (!_activeMakers.containsKey(player.getObjectId())) {
RecipeBookItemList response = new RecipeBookItemList(isDwarvenCraft, player.getMaxMp());
response.addRecipes(isDwarvenCraft ? player.getDwarvenRecipeBook() : player.getCommonRecipeBook());
player.sendPacket(response);
return;
}
player.sendPacket(SystemMessageId.CANT_ALTER_RECIPEBOOK_WHILE_CRAFTING);
}
public void requestMakeItemAbort(L2PcInstance player) {
_activeMakers.remove(player.getObjectId()); // TODO: anything else here?
}
public void requestManufactureItem(L2PcInstance manufacturer, int recipeListId, L2PcInstance player) {
final L2RecipeList recipeList = RecipeData.getInstance().getValidRecipeList(player, recipeListId);
if (recipeList == null) {
return;
}
List<L2RecipeList> dwarfRecipes = Arrays.asList(manufacturer.getDwarvenRecipeBook());
List<L2RecipeList> commonRecipes = Arrays.asList(manufacturer.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList)) {
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.");
return;
}
// Check if manufacturer is under manufacturing store or private store.
if (character().alternativeCrafting() && _activeMakers.containsKey(manufacturer.getObjectId())) {
player.sendPacket(SystemMessageId.CLOSE_STORE_WINDOW_AND_TRY_AGAIN);
return;
}
final RecipeItemMaker maker = new RecipeItemMaker(manufacturer, recipeList, player);
if (maker._isValid) {
if (character().alternativeCrafting()) {
_activeMakers.put(manufacturer.getObjectId(), maker);
ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
} else {
maker.run();
}
}
}
public void requestMakeItem(L2PcInstance player, int recipeListId) {
// Check if player is trying to operate a private store or private workshop while engaged in combat.
if (player.isInCombat() || player.isInDuel()) {
player.sendPacket(SystemMessageId.CANT_OPERATE_PRIVATE_STORE_DURING_COMBAT);
return;
}
final L2RecipeList recipeList = RecipeData.getInstance().getValidRecipeList(player, recipeListId);
if (recipeList == null) {
return;
}
List<L2RecipeList> dwarfRecipes = Arrays.asList(player.getDwarvenRecipeBook());
List<L2RecipeList> commonRecipes = Arrays.asList(player.getCommonRecipeBook());
if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList)) {
Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName() + " sent a false recipe id.");
return;
}
// Check if player is busy (possible if alt game creation is enabled)
if (character().alternativeCrafting() && _activeMakers.containsKey(player.getObjectId())) {
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1);
sm.addItemName(recipeList.getItemId());
sm.addString("You are busy creating.");
player.sendPacket(sm);
return;
}
final RecipeItemMaker maker = new RecipeItemMaker(player, recipeList, player);
if (maker._isValid) {
if (character().alternativeCrafting()) {
_activeMakers.put(player.getObjectId(), maker);
ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
} else {
maker.run();
}
}
}
private static class RecipeItemMaker implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(RecipeItemMaker.class);
protected boolean _isValid;
protected List<TempItem> _items = null;
protected final L2RecipeList _recipeList;
protected final L2PcInstance _player; // "crafter"
protected final L2PcInstance _target; // "customer"
protected final Skill _skill;
protected final int _skillId;
protected final int _skillLevel;
protected int _creationPasses = 1;
protected int _itemGrab;
protected int _exp = -1;
protected int _sp = -1;
protected long _price;
protected int _totalItems;
protected int _delay;
public RecipeItemMaker(L2PcInstance pPlayer, L2RecipeList pRecipeList, L2PcInstance pTarget) {
_player = pPlayer;
_target = pTarget;
_recipeList = pRecipeList;
_isValid = false;
_skillId = _recipeList.isDwarvenRecipe() ? CommonSkill.CREATE_DWARVEN.getId() : CommonSkill.CREATE_COMMON.getId();
_skillLevel = _player.getSkillLevel(_skillId);
_skill = _player.getKnownSkill(_skillId);
_player.isInCraftMode(true);
if (_player.isAlikeDead()) {
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isAlikeDead()) {
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_target.isProcessingTransaction()) {
_target.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
if (_player.isProcessingTransaction()) {
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// validate recipe list
if (_recipeList.getRecipes().length == 0) {
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// validate skill level
if (_recipeList.getLevel() > _skillLevel) {
_player.sendPacket(ActionFailed.STATIC_PACKET);
abort();
return;
}
// check that customer can afford to pay for creation services
if (_player != _target) {
final L2ManufactureItem item = _player.getManufactureItems().get(_recipeList.getId());
if (item != null) {
_price = item.getCost();
if (_target.getAdena() < _price) // check price
{
_target.sendPacket(SystemMessageId.YOU_NOT_ENOUGH_ADENA);
abort();
return;
}
}
}
// make temporary items
_items = listItems(false);
if (_items == null) {
abort();
return;
}
for (TempItem i : _items) {
_totalItems += i.getQuantity();
}
// initial statUse checks
if (!calculateStatUse(false, false)) {
abort();
return;
}
// initial AltStatChange checks
if (character().alternativeCrafting()) {
calculateAltStatChange();
}
updateMakeInfo(true);
updateCurMp();
updateCurLoad();
_player.isInCraftMode(false);
_isValid = true;
}
@Override
public void run() {
if (!character().crafting()) {
_target.sendMessage("Item creation is currently disabled.");
abort();
return;
}
if (_player == null) {
LOG.warn("Cannot perform craft, player is null!");
abort();
return;
}
if (_target == null) {
LOG.warn("Cannot perform craft, target is null!");
abort();
return;
}
if (!_player.isOnline()) {
LOG.warn("Cannot perform craft, player is not online!");
abort();
return;
}
if (!_target.isOnline()) {
LOG.warn("Cannot perform craft, target is not online!");
abort();
return;
}
if (character().alternativeCrafting() && !_activeMakers.containsKey(_player.getObjectId())) {
if (_target != _player) {
_target.sendMessage("Manufacture aborted!");
_player.sendMessage("Manufacture aborted!");
} else {
_player.sendMessage("Item creation aborted!");
}
abort();
return;
}
if (character().alternativeCrafting() && !_items.isEmpty()) {
if (!calculateStatUse(true, true)) {
return; // check stat use
}
updateCurMp(); // update craft window mp bar
grabSomeItems(); // grab (equip) some more items with a nice msg to player
// if still not empty, schedule another pass
if (!_items.isEmpty()) {
_delay = (int) (character().getCraftingSpeed() * _player.getMReuseRate(_skill) * GameTimeController.TICKS_PER_SECOND * GameTimeController.MILLIS_IN_TICK);
// FIXME: please fix this packet to show crafting animation (somebody)
_player.broadcastPacket(new MagicSkillUse(_player, _skillId, _skillLevel, _delay, 0));
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
} else {
// for alt mode, sleep delay msec before finishing
_player.sendPacket(new SetupGauge(0, _delay));
try {
Thread.sleep(_delay);
} catch (InterruptedException e) {
} finally {
finishCrafting();
}
}
} // for old craft mode just finish
else {
finishCrafting();
}
}
private void finishCrafting() {
if (!character().alternativeCrafting()) {
calculateStatUse(false, true);
}
// first take adena for manufacture
if ((_target != _player) && (_price > 0)) // customer must pay for services
{
// attempt to pay for item
L2ItemInstance adenatransfer = _target.transferItem("PayManufacture", _target.getInventory().getAdenaInstance().getObjectId(), _price, _player.getInventory(), _player);
if (adenatransfer == null) {
_target.sendPacket(SystemMessageId.YOU_NOT_ENOUGH_ADENA);
abort();
return;
}
}
_items = listItems(true); // this line actually takes materials from inventory
if (_items == null) {
// handle possible cheaters here
// (they click craft then try to get rid of items in order to get free craft)
} else if (Rnd.get(100) < _recipeList.getSuccessRate()) {
rewardPlayer(); // and immediately puts created item in its place
updateMakeInfo(true);
} else {
if (_target != _player) {
SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.CREATION_OF_S2_FOR_C1_AT_S3_ADENA_FAILED);
msg.addString(_target.getName());
msg.addItemName(_recipeList.getItemId());
msg.addLong(_price);
_player.sendPacket(msg);
msg = SystemMessage.getSystemMessage(SystemMessageId.C1_FAILED_TO_CREATE_S2_FOR_S3_ADENA);
msg.addString(_player.getName());
msg.addItemName(_recipeList.getItemId());
msg.addLong(_price);
_target.sendPacket(msg);
} else {
_target.sendPacket(SystemMessageId.ITEM_MIXING_FAILED);
}
updateMakeInfo(false);
}
// update load and mana bar of craft window
updateCurMp();
updateCurLoad();
_activeMakers.remove(_player.getObjectId());
_player.isInCraftMode(false);
_target.sendPacket(new ItemList(_target, false));
}
private void updateMakeInfo(boolean success) {
if (_target == _player) {
_target.sendPacket(new RecipeItemMakeInfo(_recipeList.getId(), _target, success));
} else {
_target.sendPacket(new RecipeShopItemInfo(_player, _recipeList.getId()));
}
}
private void updateCurLoad() {
StatusUpdate su = new StatusUpdate(_target);
su.addAttribute(StatusUpdate.CUR_LOAD, _target.getCurrentLoad());
_target.sendPacket(su);
}
private void updateCurMp() {
StatusUpdate su = new StatusUpdate(_target);
su.addAttribute(StatusUpdate.CUR_MP, (int) _target.getCurrentMp());
_target.sendPacket(su);
}
private void grabSomeItems() {
int grabItems = _itemGrab;
while ((grabItems > 0) && !_items.isEmpty()) {
TempItem item = _items.get(0);
int count = item.getQuantity();
if (count >= grabItems) {
count = grabItems;
}
item.setQuantity(item.getQuantity() - count);
if (item.getQuantity() <= 0) {
_items.remove(0);
} else {
_items.set(0, item);
}
grabItems -= count;
if (_target == _player) {
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_S2_EQUIPPED); // you equipped ...
sm.addLong(count);
sm.addItemName(item.getItemId());
_player.sendPacket(sm);
} else {
_target.sendMessage("Manufacturer " + _player.getName() + " used " + count + " " + item.getItemName());
}
}
}
// AltStatChange parameters make their effect here
private void calculateAltStatChange() {
_itemGrab = _skillLevel;
for (L2RecipeStatInstance altStatChange : _recipeList.getAltStatChange()) {
if (altStatChange.getType() == StatType.XP) {
_exp = altStatChange.getValue();
} else if (altStatChange.getType() == StatType.SP) {
_sp = altStatChange.getValue();
} else if (altStatChange.getType() == StatType.GIM) {
_itemGrab *= altStatChange.getValue();
}
}
// determine number of creation passes needed
_creationPasses = (_totalItems / _itemGrab) + ((_totalItems % _itemGrab) != 0 ? 1 : 0);
if (_creationPasses < 1) {
_creationPasses = 1;
}
}
// StatUse
private boolean calculateStatUse(boolean isWait, boolean isReduce) {
boolean ret = true;
for (L2RecipeStatInstance statUse : _recipeList.getStatUse()) {
double modifiedValue = statUse.getValue() / _creationPasses;
if (statUse.getType() == StatType.HP) {
// we do not want to kill the player, so its CurrentHP must be greater than the reduce value
if (_player.getCurrentHp() <= modifiedValue) {
// rest (wait for HP)
if (character().alternativeCrafting() && isWait) {
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
} else {
_target.sendPacket(SystemMessageId.NOT_ENOUGH_HP);
abort();
}
ret = false;
} else if (isReduce) {
_player.reduceCurrentHp(modifiedValue, _player, _skill);
}
} else if (statUse.getType() == StatType.MP) {
if (_player.getCurrentMp() < modifiedValue) {
// rest (wait for MP)
if (character().alternativeCrafting() && isWait) {
_player.sendPacket(new SetupGauge(0, _delay));
ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
} else {
_target.sendPacket(SystemMessageId.NOT_ENOUGH_MP);
abort();
}
ret = false;
} else if (isReduce) {
_player.reduceCurrentMp(modifiedValue);
}
} else {
// there is an unknown StatUse value
_target.sendMessage("Recipe error!!!, please tell this to your GM.");
ret = false;
abort();
}
}
return ret;
}
private List<TempItem> listItems(boolean remove) {
L2RecipeInstance[] recipes = _recipeList.getRecipes();
Inventory inv = _target.getInventory();
List<TempItem> materials = new ArrayList<>();
SystemMessage sm;
for (L2RecipeInstance recipe : recipes) {
if (recipe.getQuantity() > 0) {
L2ItemInstance item = inv.getItemByItemId(recipe.getItemId());
long itemQuantityAmount = item == null ? 0 : item.getCount();
// check materials
if (itemQuantityAmount < recipe.getQuantity()) {
sm = SystemMessage.getSystemMessage(SystemMessageId.MISSING_S2_S1_TO_CREATE);
sm.addItemName(recipe.getItemId());
sm.addLong(recipe.getQuantity() - itemQuantityAmount);
_target.sendPacket(sm);
abort();
return null;
}
// make new temporary object, just for counting purposes
materials.add(new TempItem(item, recipe.getQuantity()));
}
}
if (remove) {
for (TempItem tmp : materials) {
inv.destroyItemByItemId("Manufacture", tmp.getItemId(), tmp.getQuantity(), _target, _player);
if (tmp.getQuantity() > 1) {
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1_DISAPPEARED);
sm.addItemName(tmp.getItemId());
sm.addLong(tmp.getQuantity());
_target.sendPacket(sm);
} else {
sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
sm.addItemName(tmp.getItemId());
_target.sendPacket(sm);
}
}
}
return materials;
}
private void abort() {
updateMakeInfo(false);
_player.isInCraftMode(false);
_activeMakers.remove(_player.getObjectId());
}
private void rewardPlayer() {
int rareProdId = _recipeList.getRareItemId();
int itemId = _recipeList.getItemId();
int itemCount = _recipeList.getCount();
L2Item template = ItemTable.getInstance().getTemplate(itemId);
// check that the current recipe has a rare production or not
if ((rareProdId != -1) && ((rareProdId == itemId) || character().craftMasterwork())) {
if (Rnd.get(100) < _recipeList.getRarity()) {
itemId = rareProdId;
itemCount = _recipeList.getRareCount();
}
}
_target.getInventory().addItem("Manufacture", itemId, itemCount, _target, _player);
// inform customer of earned item
SystemMessage sm;
if (_target != _player) {
// inform manufacturer of earned profit
if (itemCount == 1) {
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_CREATED_FOR_C1_FOR_S3_ADENA);
sm.addString(_target.getName());
sm.addItemName(itemId);
sm.addLong(_price);
_player.sendPacket(sm);
sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_FOR_S3_ADENA);
sm.addString(_player.getName());
sm.addItemName(itemId);
sm.addLong(_price);
_target.sendPacket(sm);
} else {
sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S3_S_CREATED_FOR_C1_FOR_S4_ADENA);
sm.addString(_target.getName());
sm.addInt(itemCount);
sm.addItemName(itemId);
sm.addLong(_price);
_player.sendPacket(sm);
sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_S3_S_FOR_S4_ADENA);
sm.addString(_player.getName());
sm.addInt(itemCount);
sm.addItemName(itemId);
sm.addLong(_price);
_target.sendPacket(sm);
}
}
if (itemCount > 1) {
sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_S2_S1_S);
sm.addItemName(itemId);
sm.addLong(itemCount);
_target.sendPacket(sm);
} else {
sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1);
sm.addItemName(itemId);
_target.sendPacket(sm);
}
if (character().alternativeCrafting()) {
int recipeLevel = _recipeList.getLevel();
if (_exp < 0) {
_exp = template.getReferencePrice() * itemCount;
_exp /= recipeLevel;
}
if (_sp < 0) {
_sp = _exp / 10;
}
if (itemId == rareProdId) {
_exp *= character().getCraftingRareXpRate();
_sp *= character().getCraftingRareSpRate();
}
if (_exp < 0) {
_exp = 0;
}
if (_sp < 0) {
_sp = 0;
}
for (int i = _skillLevel; i > recipeLevel; i--) {
_exp /= 4;
_sp /= 4;
}
// Added multiplication of Creation speed with XP/SP gain slower crafting -> more XP,
// faster crafting -> less XP you can use ALT_GAME_CREATION_XP_RATE/SP to modify XP/SP gained (default = 1)
_player.addExpAndSp((long) (_exp * character().getCraftingXpRate() * character().getCraftingSpeed()), //
(int) (_sp * character().getCraftingSpRate() * character().getCraftingSpeed()));
}
updateMakeInfo(true); // success
}
}
public static RecipeController getInstance() {
return SingletonHolder._instance;
}
private static class SingletonHolder {
protected static final RecipeController _instance = new RecipeController();
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,517 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.customs;
import static com.l2jserver.gameserver.config.Configuration.general;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.UPnPService;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.gameserver.data.sql.impl.ClanTable;
import com.l2jserver.gameserver.data.sql.impl.OfflineTradersTable;
import com.l2jserver.gameserver.datatables.BotReportTable;
import com.l2jserver.gameserver.instancemanager.CastleManorManager;
import com.l2jserver.gameserver.instancemanager.ClanHallSiegeManager;
import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
import com.l2jserver.gameserver.instancemanager.GlobalVariablesManager;
import com.l2jserver.gameserver.instancemanager.GrandBossManager;
import com.l2jserver.gameserver.instancemanager.ItemAuctionManager;
import com.l2jserver.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2jserver.gameserver.instancemanager.QuestManager;
import com.l2jserver.gameserver.instancemanager.RaidBossSpawnManager;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.entity.Hero;
import com.l2jserver.gameserver.model.olympiad.Olympiad;
import com.l2jserver.gameserver.network.L2GameClient;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.gameserverpackets.ServerStatus;
import com.l2jserver.gameserver.network.serverpackets.ServerClose;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.util.Broadcast;
/**
* This class provides the functions for shutting down and restarting the server.<br>
* It closes all open client connections and saves all data.
* @since 2005/03/27 15:29:09
*/
public class Shutdown extends Thread {
private static final Logger LOG = LoggerFactory.getLogger(Shutdown.class);
private static Shutdown _counterInstance = null;
private int _secondsShut;
private int _shutdownMode;
private static final int SIGTERM = 0;
private static final int GM_SHUTDOWN = 1;
private static final int GM_RESTART = 2;
private static final int ABORT = 3;
private static final String[] MODE_TEXT = {
"SIGTERM",
"shutting down",
"restarting",
"aborting"
};
/**
* This function starts a shutdown count down from Telnet (Copied from Function startShutdown())
* @param seconds seconds until shutdown
*/
private void SendServerQuit(int seconds) {
SystemMessage sysm = SystemMessage.getSystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECONDS);
sysm.addInt(seconds);
Broadcast.toAllOnlinePlayers(sysm);
}
public void startTelnetShutdown(String IP, int seconds, boolean restart) {
LOG.warn("IP: {} issued shutdown command. {} in {} seconds!", IP, MODE_TEXT[_shutdownMode], seconds);
if (restart) {
_shutdownMode = GM_RESTART;
} else {
_shutdownMode = GM_SHUTDOWN;
}
switch (seconds) {
case 540:
case 480:
case 420:
case 360:
case 300:
case 240:
case 180:
case 120:
case 60:
case 30:
case 10:
case 5:
case 4:
case 3:
case 2:
case 1:
break;
default:
SendServerQuit(seconds);
}
if (_counterInstance != null) {
_counterInstance._abort();
}
_counterInstance = new Shutdown(seconds, restart);
_counterInstance.start();
}
/**
* This function aborts a running countdown
* @param IP IP Which Issued shutdown command
*/
public void telnetAbort(String IP) {
LOG.warn("IP: {} issued shutdown ABORT. {} has been stopped!", IP, MODE_TEXT[_shutdownMode]);
if (_counterInstance != null) {
_counterInstance._abort();
Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
}
}
/**
* Default constructor is only used internal to create the shutdown-hook instance
*/
protected Shutdown() {
_secondsShut = -1;
_shutdownMode = SIGTERM;
}
/**
* This creates a countdown instance of Shutdown.
* @param seconds how many seconds until shutdown
* @param restart true is the server shall restart after shutdown
*/
public Shutdown(int seconds, boolean restart) {
if (seconds < 0) {
seconds = 0;
}
_secondsShut = seconds;
if (restart) {
_shutdownMode = GM_RESTART;
} else {
_shutdownMode = GM_SHUTDOWN;
}
}
/**
* This function is called, when a new thread starts if this thread is the thread of getInstance, then this is the shutdown hook and we save all data and disconnect all clients.<br>
* After this thread ends, the server will completely exit if this is not the thread of getInstance, then this is a countdown thread.<br>
* We start the countdown, and when we finished it, and it was not aborted, we tell the shutdown-hook why we call exit, and then call exit when the exit status of the server is 1, startServer.sh / startServer.bat will restart the server.
*/
@Override
public void run() {
if (this == getInstance()) {
TimeCounter tc = new TimeCounter();
TimeCounter tc1 = new TimeCounter();
try {
UPnPService.getInstance().removeAllPorts();
LOG.info("UPnP Service: All ports mappings deleted ({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
LOG.warn("Error while removing UPnP port mappings!", e);
}
try {
if ((customs().offlineTradeEnable() || customs().offlineCraftEnable()) && customs().restoreOffliners()) {
OfflineTradersTable.getInstance().storeOffliners();
LOG.info("Offline Traders Table: Offline shops stored({}ms).", tc.getEstimatedTimeAndRestartCounter());
}
} catch (Exception e) {
LOG.warn("Error saving offline shops!", e);
}
try {
disconnectAllCharacters();
LOG.info("All players disconnected and saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
// ignore
}
// ensure all services are stopped
try {
GameTimeController.getInstance().stopTimer();
LOG.info("Game Time Controller: Timer stopped({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
// ignore
}
// stop all thread pools
try {
ThreadPoolManager.getInstance().shutdown();
LOG.info("Thread Pool Manager: Manager has been shut down({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
// ignore
}
try {
LoginServerThread.getInstance().interrupt();
LOG.info("Login Server Thread: Thread interrupted({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
// ignore
}
// last bye bye, save all data and quit this server
saveData();
tc.restartCounter();
// saveData sends messages to exit players, so shutdown selector after it
try {
GameServer.gameServer.getSelectorThread().shutdown();
LOG.info("Game Server: Selector thread has been shut down({}ms).", tc.getEstimatedTimeAndRestartCounter());
} catch (Exception e) {
// ignore
}
// commit data, last chance
ConnectionFactory.getInstance().close();
LOG.info("ConnectionFactory: Database connection has been shutdown({}ms).", tc.getEstimatedTimeAndRestartCounter());
// server will quit, when this function ends.
if (getInstance()._shutdownMode == GM_RESTART) {
Runtime.getRuntime().halt(2);
} else {
Runtime.getRuntime().halt(0);
}
LOG.info("The server has been successfully shut down in {}seconds.", TimeUnit.MILLISECONDS.toSeconds(tc1.getEstimatedTime()));
} else {
// gm shutdown: send warnings and then call exit to start shutdown sequence
countdown();
// last point where logging is operational :(
LOG.warn("GM shutdown countdown is over. {} NOW!", MODE_TEXT[_shutdownMode]);
switch (_shutdownMode) {
case GM_SHUTDOWN -> {
getInstance().setMode(GM_SHUTDOWN);
System.exit(0);
}
case GM_RESTART -> {
getInstance().setMode(GM_RESTART);
System.exit(2);
}
case ABORT -> LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_AUTO);
}
}
}
/**
* This functions starts a shutdown countdown.
* @param activeChar GM who issued the shutdown command
* @param seconds seconds until shutdown
* @param restart true if the server will restart after shutdown
*/
public void startShutdown(L2PcInstance activeChar, int seconds, boolean restart) {
if (restart) {
_shutdownMode = GM_RESTART;
} else {
_shutdownMode = GM_SHUTDOWN;
}
LOG.warn("GM: {}({}) issued shutdown command. {} in {} seconds!", activeChar.getName(), activeChar.getObjectId(), MODE_TEXT[_shutdownMode], seconds);
if (_shutdownMode > 0) {
switch (seconds) {
case 540:
case 480:
case 420:
case 360:
case 300:
case 240:
case 180:
case 120:
case 60:
case 30:
case 10:
case 5:
case 4:
case 3:
case 2:
case 1:
break;
default:
SendServerQuit(seconds);
}
}
if (_counterInstance != null) {
_counterInstance._abort();
}
// the main instance should only run for shutdown hook, so we start a new instance
_counterInstance = new Shutdown(seconds, restart);
_counterInstance.start();
}
/**
* This function aborts a running countdown.
* @param activeChar GM who issued the abort command
*/
public void abort(L2PcInstance activeChar) {
LOG.warn("GM: {}({}) issued shutdown ABORT. {} has been stopped!", activeChar.getName(), activeChar.getObjectId(), MODE_TEXT[_shutdownMode]);
if (_counterInstance != null) {
_counterInstance._abort();
Broadcast.toAllOnlinePlayers("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!", false);
}
}
/**
* Set the shutdown mode.
* @param mode what mode shall be set
*/
private void setMode(int mode) {
_shutdownMode = mode;
}
/**
* Set shutdown mode to ABORT.
*/
private void _abort() {
_shutdownMode = ABORT;
}
/**
* This counts the countdown and reports it to all players countdown is aborted if mode changes to ABORT.
*/
private void countdown() {
try {
while (_secondsShut > 0) {
switch (_secondsShut) {
case 540 -> SendServerQuit(540);
case 480 -> SendServerQuit(480);
case 420 -> SendServerQuit(420);
case 360 -> SendServerQuit(360);
case 300 -> SendServerQuit(300);
case 240 -> SendServerQuit(240);
case 180 -> SendServerQuit(180);
case 120 -> SendServerQuit(120);
case 60 -> {
LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN); // avoids new players from logging in
SendServerQuit(60);
}
case 30 -> SendServerQuit(30);
case 10 -> SendServerQuit(10);
case 5 -> SendServerQuit(5);
case 4 -> SendServerQuit(4);
case 3 -> SendServerQuit(3);
case 2 -> SendServerQuit(2);
case 1 -> SendServerQuit(1);
}
_secondsShut--;
int delay = 1000; // milliseconds
Thread.sleep(delay);
if (_shutdownMode == ABORT) {
break;
}
}
} catch (InterruptedException e) {
// this will never happen
}
}
/**
* This sends a last bye bye, disconnects all players and saves data.
*/
private void saveData() {
switch (_shutdownMode) {
case SIGTERM -> LOG.info("SIGTERM received. Shutting down NOW!");
case GM_SHUTDOWN -> LOG.info("GM shutdown received. Shutting down NOW!");
case GM_RESTART -> LOG.info("GM restart received. Restarting NOW!");
}
TimeCounter tc = new TimeCounter();
// Seven Signs data is now saved along with Festival data.
if (!SevenSigns.getInstance().isSealValidationPeriod()) {
SevenSignsFestival.getInstance().saveFestivalData(false);
LOG.info("SevenSignsFestival: Festival data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
}
// Save Seven Signs data before closing. :)
SevenSigns.getInstance().saveSevenSignsData();
LOG.info("SevenSigns: Seven Signs data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
SevenSigns.getInstance().saveSevenSignsStatus();
LOG.info("SevenSigns: Seven Signs status saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
// Save all raidboss and GrandBoss status ^_^
RaidBossSpawnManager.getInstance().cleanUp();
LOG.info("RaidBossSpawnManager: All raidboss info saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
GrandBossManager.getInstance().cleanUp();
LOG.info("GrandBossManager: All Grand Boss info saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
ItemAuctionManager.getInstance().shutdown();
LOG.info("Item Auction Manager: All tasks stopped({}ms).", tc.getEstimatedTimeAndRestartCounter());
Olympiad.getInstance().saveOlympiadStatus();
LOG.info("Olympiad System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
Hero.getInstance().shutdown();
LOG.info("Hero System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
ClanTable.getInstance().storeClanScore();
LOG.info("Clan System: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
// Save Cursed Weapons data before closing.
CursedWeaponsManager.getInstance().saveData();
LOG.info("Cursed Weapons Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
// Save all manor data
if (!general().manorSaveAllActions()) {
CastleManorManager.getInstance().storeMe();
LOG.info("Castle Manor Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
}
ClanHallSiegeManager.getInstance().onServerShutDown();
LOG.info("CHSiegeManager: Siegable hall attacker lists saved!");
// Save all global (non-player specific) Quest data that needs to persist after reboot
QuestManager.getInstance().save();
LOG.info("Quest Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
// Save all global variables data
GlobalVariablesManager.getInstance().storeMe();
LOG.info("Global Variables Manager: Variables saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
// Save items on ground before closing
if (general().saveDroppedItem()) {
ItemsOnGroundManager.getInstance().saveInDb();
LOG.info("Items On Ground Manager: Data saved({}ms).", tc.getEstimatedTimeAndRestartCounter());
ItemsOnGroundManager.getInstance().cleanUp();
LOG.info("Items On Ground Manager: Cleaned up({}ms).", tc.getEstimatedTimeAndRestartCounter());
}
// Save bot reports to database
if (general().enableBotReportButton()) {
BotReportTable.getInstance().saveReportedCharData();
LOG.info("Bot Report Table: Successfully saved reports to database!");
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// never happens :p
}
}
/**
* This disconnects all clients from the server.
*/
private void disconnectAllCharacters() {
for (L2PcInstance player : L2World.getInstance().getPlayers()) {
// Logout Character
try {
L2GameClient client = player.getClient();
if ((client != null) && !client.isDetached()) {
client.close(ServerClose.STATIC_PACKET);
client.setActiveChar(null);
player.setClient(null);
}
player.deleteMe();
} catch (Exception e) {
LOG.warn("Failed logout char {}", player, e);
}
}
}
/**
* A simple class used to track down the estimated time of method executions.<br>
* Once this class is created, it saves the start time, and when you want to get the estimated time, use the getEstimatedTime() method.
*/
private static final class TimeCounter {
private long _startTime;
protected TimeCounter() {
restartCounter();
}
protected void restartCounter() {
_startTime = System.currentTimeMillis();
}
protected long getEstimatedTimeAndRestartCounter() {
final long toReturn = System.currentTimeMillis() - _startTime;
restartCounter();
return toReturn;
}
protected long getEstimatedTime() {
return System.currentTimeMillis() - _startTime;
}
}
/**
* Get the shutdown-hook instance the shutdown-hook instance is created by the first call of this function, but it has to be registered externally.<br>
* @return instance of Shutdown, to be used as shutdown hook
*/
public static Shutdown getInstance() {
return SingletonHolder._instance;
}
private static class SingletonHolder {
protected static final Shutdown _instance = new Shutdown();
}
}
@@ -0,0 +1,630 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver;
import static com.l2jserver.gameserver.config.Configuration.general;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.config.Configuration;
import com.l2jserver.gameserver.util.StringUtil;
/**
* <p>
* This class is made to handle all the ThreadPools used in L2J.
* </p>
* <p>
* Scheduled Tasks can either be sent to a {@link #_generalScheduledThreadPool "general"} or {@link #_effectsScheduledThreadPool "effects"} {@link ScheduledThreadPoolExecutor ScheduledThreadPool}: The "effects" one is used for every effects (skills, hp/mp regen ...) while the "general" one is used
* for everything else that needs to be scheduled.<br>
* There also is an {@link #_aiScheduledThreadPool "ai"} {@link ScheduledThreadPoolExecutor ScheduledThreadPool} used for AI Tasks.
* </p>
* <p>
* Tasks can be sent to {@link ScheduledThreadPoolExecutor ScheduledThreadPool} either with:
* <ul>
* <li>{@link #scheduleEffect(Runnable, long, TimeUnit)} and {@link #scheduleEffect(Runnable, long)} : for effects Tasks that needs to be executed only once.</li>
* <li>{@link #scheduleGeneral(Runnable, long, TimeUnit)} and {@link #scheduleGeneral(Runnable, long)} : for scheduled Tasks that needs to be executed once.</li>
* <li>{@link #scheduleAi(Runnable, long, TimeUnit)} and {@link #scheduleAi(Runnable, long)} : for AI Tasks that needs to be executed once</li>
* </ul>
* or
* <ul>
* <li>{@link #scheduleEffectAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleEffectAtFixedRate(Runnable, long, long)} : for effects Tasks that needs to be executed periodically.</li>
* <li>{@link #scheduleGeneralAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleGeneralAtFixedRate(Runnable, long, long)} : for scheduled Tasks that needs to be executed periodically.</li>
* <li>{@link #scheduleAiAtFixedRate(Runnable, long, long, TimeUnit)} and {@link #scheduleAiAtFixedRate(Runnable, long, long)} : for AI Tasks that needs to be executed periodically</li>
* </ul>
* </p>
* <p>
* For all Tasks that should be executed with no delay asynchronously in a ThreadPool there also are usual {@link ThreadPoolExecutor ThreadPools} that can grow/shrink according to their load.:
* <ul>
* <li>{@link #_generalPacketsThreadPool GeneralPackets} where most packets handler are executed.</li>
* <li>{@link #_ioPacketsThreadPool I/O Packets} where all the i/o packets are executed.</li>
* <li>There will be an AI ThreadPool where AI events should be executed</li>
* <li>A general ThreadPool where everything else that needs to run asynchronously with no delay should be executed ({@link com.l2jserver.gameserver.model.actor.knownlist KnownList} updates, SQL updates/inserts...)?</li>
* </ul>
* </p>
* @author -Wooden-
*/
public class ThreadPoolManager {
protected static final Logger LOG = LoggerFactory.getLogger(ThreadPoolManager.class);
private static final class RunnableWrapper implements Runnable {
private final Runnable _r;
public RunnableWrapper(final Runnable r) {
_r = r;
}
@Override
public void run() {
try {
_r.run();
} catch (final Throwable e) {
final Thread t = Thread.currentThread();
final UncaughtExceptionHandler h = t.getUncaughtExceptionHandler();
if (h != null) {
h.uncaughtException(t, e);
}
}
}
}
private final ScheduledThreadPoolExecutor _effectsScheduledThreadPool;
private final ScheduledThreadPoolExecutor _generalScheduledThreadPool;
private final ScheduledThreadPoolExecutor _aiScheduledThreadPool;
private final ScheduledThreadPoolExecutor _eventScheduledThreadPool;
private final ThreadPoolExecutor _generalPacketsThreadPool;
private final ThreadPoolExecutor _ioPacketsThreadPool;
private final ThreadPoolExecutor _generalThreadPool;
private final ThreadPoolExecutor _eventThreadPool;
private boolean _shutdown;
public static ThreadPoolManager getInstance() {
return SingletonHolder._instance;
}
protected ThreadPoolManager() {
_effectsScheduledThreadPool = new ScheduledThreadPoolExecutor(general().getThreadPoolSizeEffects(), new PriorityThreadFactory("EffectsSTPool", Thread.NORM_PRIORITY));
_generalScheduledThreadPool = new ScheduledThreadPoolExecutor(general().getThreadPoolSizeGeneral(), new PriorityThreadFactory("GeneralSTPool", Thread.NORM_PRIORITY));
_eventScheduledThreadPool = new ScheduledThreadPoolExecutor(general().getThreadPoolSizeEvents(), new PriorityThreadFactory("EventSTPool", Thread.NORM_PRIORITY));
_ioPacketsThreadPool = new ThreadPoolExecutor(general().getUrgentPacketThreadCoreSize(), Integer.MAX_VALUE, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PriorityThreadFactory("I/O Packet Pool", Thread.NORM_PRIORITY + 1));
_generalPacketsThreadPool = new ThreadPoolExecutor(general().getGeneralPacketThreadCoreSize(), general().getGeneralPacketThreadCoreSize() + 2, 15L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PriorityThreadFactory("Normal Packet Pool", Thread.NORM_PRIORITY + 1));
_generalThreadPool = new ThreadPoolExecutor(general().getGeneralThreadCoreSize(), general().getGeneralThreadCoreSize() + 2, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PriorityThreadFactory("General Pool", Thread.NORM_PRIORITY));
_aiScheduledThreadPool = new ScheduledThreadPoolExecutor(general().getAiMaxThread(), new PriorityThreadFactory("AISTPool", Thread.NORM_PRIORITY));
_eventThreadPool = new ThreadPoolExecutor(general().getEventsMaxThread(), general().getEventsMaxThread() + 2, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PriorityThreadFactory("Event Pool", Thread.NORM_PRIORITY));
scheduleGeneralAtFixedRate(new PurgeTask(_effectsScheduledThreadPool, _generalScheduledThreadPool, _aiScheduledThreadPool, _eventThreadPool), 10, 5, TimeUnit.MINUTES);
}
/**
* Schedules an effect task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffect(Runnable task, long delay, TimeUnit unit) {
try {
return _effectsScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
} catch (RejectedExecutionException e) {
return null;
}
}
/**
* Schedules an effect task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffect(Runnable task, long delay) {
return scheduleEffect(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules an effect task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffectAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
try {
return _effectsScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules an effect task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEffectAtFixedRate(Runnable task, long initialDelay, long period) {
return scheduleEffectAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneral(Runnable task, long delay, TimeUnit unit) {
try {
return _generalScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneral(Runnable task, long delay) {
return scheduleGeneral(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneralAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
try {
return _generalScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules a event task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEvent(Runnable task, long delay, TimeUnit unit) {
try {
return _eventScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules a event task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEvent(Runnable task, long delay) {
return scheduleEvent(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a event task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleEventAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
try {
return _eventScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleGeneralAtFixedRate(Runnable task, long initialDelay, long period) {
return scheduleGeneralAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Schedules an AI task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in the given time unit
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAi(Runnable task, long delay, TimeUnit unit) {
try {
return _aiScheduledThreadPool.schedule(new RunnableWrapper(task), delay, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules an AI task to be executed after the given delay.
* @param task the task to execute
* @param delay the delay in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAi(Runnable task, long delay) {
return scheduleAi(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in the given time unit
* @param period the period between executions in the given time unit
* @param unit the time unit of the initialDelay and period parameters
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAiAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
try {
return _aiScheduledThreadPool.scheduleAtFixedRate(new RunnableWrapper(task), initialDelay, period, unit);
} catch (RejectedExecutionException e) {
return null; /* shutdown, ignore */
}
}
/**
* Schedules a general task to be executed at fixed rate.
* @param task the task to execute
* @param initialDelay the initial delay in milliseconds
* @param period the period between executions in milliseconds
* @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an exception upon cancellation
*/
public ScheduledFuture<?> scheduleAiAtFixedRate(Runnable task, long initialDelay, long period) {
return scheduleAiAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* Executes a packet task sometime in future in another thread.
* @param task the task to execute
*/
public void executePacket(Runnable task) {
try {
_generalPacketsThreadPool.execute(task);
} catch (RejectedExecutionException e) {
/* shutdown, ignore */
}
}
/**
* Executes an IO packet task sometime in future in another thread.
* @param task the task to execute
*/
public void executeIOPacket(Runnable task) {
try {
_ioPacketsThreadPool.execute(task);
} catch (RejectedExecutionException e) {
/* shutdown, ignore */
}
}
/**
* Executes a general task sometime in future in another thread.
* @param task the task to execute
*/
public void executeGeneral(Runnable task) {
try {
_generalThreadPool.execute(new RunnableWrapper(task));
} catch (RejectedExecutionException e) {
/* shutdown, ignore */
}
}
/**
* Executes an AI task sometime in future in another thread.
* @param task the task to execute
*/
public void executeAi(Runnable task) {
try {
_aiScheduledThreadPool.execute(new RunnableWrapper(task));
} catch (RejectedExecutionException e) {
/* shutdown, ignore */
}
}
/**
* Executes an Event task sometime in future in another thread.
* @param task the task to execute
*/
public void executeEvent(Runnable task) {
try {
_eventThreadPool.execute(new RunnableWrapper(task));
} catch (RejectedExecutionException e) {
/* shutdown, ignore */
}
}
public String[] getStats() {
return new String[] {
"STP:",
" + Effects:",
" |- ActiveThreads: " + _effectsScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _effectsScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _effectsScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _effectsScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _effectsScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _effectsScheduledThreadPool.getQueue().size(),
" | -------",
" + General:",
" |- ActiveThreads: " + _generalScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _generalScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _generalScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _generalScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _generalScheduledThreadPool.getQueue().size(),
" | -------",
" + AI:",
" |- ActiveThreads: " + _aiScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _aiScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _aiScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _aiScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _aiScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _aiScheduledThreadPool.getQueue().size(),
" | -------",
" + Event:",
" |- ActiveThreads: " + _eventScheduledThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _eventScheduledThreadPool.getCorePoolSize(),
" |- PoolSize: " + _eventScheduledThreadPool.getPoolSize(),
" |- MaximumPoolSize: " + _eventScheduledThreadPool.getMaximumPoolSize(),
" |- CompletedTasks: " + _eventScheduledThreadPool.getCompletedTaskCount(),
" |- ScheduledTasks: " + _eventScheduledThreadPool.getQueue().size(),
"TP:",
" + Packets:",
" |- ActiveThreads: " + _generalPacketsThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalPacketsThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _generalPacketsThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _generalPacketsThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _generalPacketsThreadPool.getPoolSize(),
" |- CompletedTasks: " + _generalPacketsThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _generalPacketsThreadPool.getQueue().size(),
" | -------",
" + I/O Packets:",
" |- ActiveThreads: " + _ioPacketsThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _ioPacketsThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _ioPacketsThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _ioPacketsThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _ioPacketsThreadPool.getPoolSize(),
" |- CompletedTasks: " + _ioPacketsThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _ioPacketsThreadPool.getQueue().size(),
" | -------",
" + General Tasks:",
" |- ActiveThreads: " + _generalThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _generalThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _generalThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _generalThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _generalThreadPool.getPoolSize(),
" |- CompletedTasks: " + _generalThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _generalThreadPool.getQueue().size(),
" | -------",
" + Event Tasks:",
" |- ActiveThreads: " + _eventThreadPool.getActiveCount(),
" |- getCorePoolSize: " + _eventThreadPool.getCorePoolSize(),
" |- MaximumPoolSize: " + _eventThreadPool.getMaximumPoolSize(),
" |- LargestPoolSize: " + _eventThreadPool.getLargestPoolSize(),
" |- PoolSize: " + _eventThreadPool.getPoolSize(),
" |- CompletedTasks: " + _eventThreadPool.getCompletedTaskCount(),
" |- QueuedTasks: " + _eventThreadPool.getQueue().size(),
" | -------"
};
}
private static class PriorityThreadFactory implements ThreadFactory {
private final int _priority;
private final String _name;
private final AtomicInteger _threadNumber = new AtomicInteger(1);
private final ThreadGroup _group;
public PriorityThreadFactory(String name, int priority) {
_priority = priority;
_name = name;
_group = new ThreadGroup(_name);
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(_group, r, _name + "-" + _threadNumber.getAndIncrement());
t.setPriority(_priority);
return t;
}
public ThreadGroup getGroup() {
return _group;
}
}
public void shutdown() {
_shutdown = true;
try {
_effectsScheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalScheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_ioPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_generalThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_eventThreadPool.awaitTermination(1, TimeUnit.SECONDS);
_effectsScheduledThreadPool.shutdown();
_generalScheduledThreadPool.shutdown();
_generalPacketsThreadPool.shutdown();
_ioPacketsThreadPool.shutdown();
_generalThreadPool.shutdown();
_eventThreadPool.shutdown();
LOG.info("All ThreadPools are now stopped");
} catch (InterruptedException e) {
LOG.warn("There has been a problem shutting down the thread pool manager!", e);
}
}
public boolean isShutdown() {
return _shutdown;
}
public void purge() {
_effectsScheduledThreadPool.purge();
_generalScheduledThreadPool.purge();
_aiScheduledThreadPool.purge();
_eventScheduledThreadPool.purge();
_ioPacketsThreadPool.purge();
_generalPacketsThreadPool.purge();
_generalThreadPool.purge();
_eventThreadPool.purge();
}
public String getPacketStats() {
final StringBuilder sb = new StringBuilder(1000);
ThreadFactory tf = _generalPacketsThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory ptf) {
int count = ptf.getGroup().activeCount();
Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "General Packet Thread Pool:" + Configuration.EOL + "Tasks in the queue: ", String.valueOf(_generalPacketsThreadPool.getQueue().size()), Configuration.EOL + "Showing threads stack trace:" + Configuration.EOL + "There should be ", String.valueOf(count), " Threads"
+ Configuration.EOL);
for (Thread t : threads) {
if (t == null) {
continue;
}
StringUtil.append(sb, t.getName(), Configuration.EOL);
for (StackTraceElement ste : t.getStackTrace()) {
StringUtil.append(sb, ste.toString(), Configuration.EOL);
}
}
}
sb.append("Packet Tp stack traces printed.");
sb.append(Configuration.EOL);
return sb.toString();
}
public String getIOPacketStats() {
final StringBuilder sb = new StringBuilder(1000);
ThreadFactory tf = _ioPacketsThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory ptf) {
int count = ptf.getGroup().activeCount();
Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "I/O Packet Thread Pool:" + Configuration.EOL + "Tasks in the queue: ", String.valueOf(_ioPacketsThreadPool.getQueue().size()), Configuration.EOL + "Showing threads stack trace:" + Configuration.EOL + "There should be ", String.valueOf(count), " Threads"
+ Configuration.EOL);
for (Thread t : threads) {
if (t == null) {
continue;
}
StringUtil.append(sb, t.getName(), Configuration.EOL);
for (StackTraceElement ste : t.getStackTrace()) {
StringUtil.append(sb, ste.toString(), Configuration.EOL);
}
}
}
sb.append("Packet Tp stack traces printed.");
sb.append(Configuration.EOL);
return sb.toString();
}
public String getGeneralStats() {
final StringBuilder sb = new StringBuilder(1000);
ThreadFactory tf = _generalThreadPool.getThreadFactory();
if (tf instanceof PriorityThreadFactory ptf) {
int count = ptf.getGroup().activeCount();
Thread[] threads = new Thread[count + 2];
ptf.getGroup().enumerate(threads);
StringUtil.append(sb, "General Thread Pool:" + Configuration.EOL + "Tasks in the queue: ", String.valueOf(_generalThreadPool.getQueue().size()), Configuration.EOL + "Showing threads stack trace:" + Configuration.EOL + "There should be ", String.valueOf(count), " Threads"
+ Configuration.EOL);
for (Thread t : threads) {
if (t == null) {
continue;
}
StringUtil.append(sb, t.getName(), Configuration.EOL);
for (StackTraceElement ste : t.getStackTrace()) {
StringUtil.append(sb, ste.toString(), Configuration.EOL);
}
}
}
sb.append("Packet Tp stack traces printed.");
sb.append(Configuration.EOL);
return sb.toString();
}
protected static class PurgeTask implements Runnable {
private final ScheduledThreadPoolExecutor _effectsScheduled;
private final ScheduledThreadPoolExecutor _generalScheduled;
private final ScheduledThreadPoolExecutor _aiScheduled;
private final ThreadPoolExecutor _eventScheduled;
PurgeTask(ScheduledThreadPoolExecutor effectsScheduledThreadPool, ScheduledThreadPoolExecutor generalScheduledThreadPool, //
ScheduledThreadPoolExecutor aiScheduledThreadPool, ThreadPoolExecutor eventScheduledThreadPool) {
_effectsScheduled = effectsScheduledThreadPool;
_generalScheduled = generalScheduledThreadPool;
_aiScheduled = aiScheduledThreadPool;
_eventScheduled = eventScheduledThreadPool;
}
@Override
public void run() {
_effectsScheduled.purge();
_generalScheduled.purge();
_aiScheduled.purge();
_eventScheduled.purge();
}
}
private static class SingletonHolder {
protected static final ThreadPoolManager _instance = new ThreadPoolManager();
}
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.agathion;
/**
* Agathion.
* @author Zoey76
* @version 2.6.2.0
*/
public record Agathion(int npcId, int id, int itemId, int energy, int maxEnergy) {
}
@@ -0,0 +1,93 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.agathion.repository;
import static com.l2jserver.gameserver.config.Configuration.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.l2jserver.gameserver.agathion.Agathion;
/**
* Agathion repository.
* @author Zoey76
* @version 2.6.3.0
*/
public class AgathionRepository {
private static final Logger LOG = LoggerFactory.getLogger(AgathionRepository.class);
private static final Gson GSON = new Gson();
private static final Type TYPE_LIST_AGATHION = new TypeToken<List<Agathion>>() {
}.getType();
private final Map<Integer, Agathion> agathions = new HashMap<>();
private final Map<Integer, Integer> itemIdToNpcId = new HashMap<>();
private AgathionRepository() {
load();
}
private void load() {
agathions.clear();
itemIdToNpcId.clear();
final var file = new File(server().getDatapackRoot(), "data/stats/agathions.json");
try (var reader = new JsonReader(new FileReader(file))) {
final List<Agathion> loaded = GSON.fromJson(reader, TYPE_LIST_AGATHION);
if (loaded != null) {
for (var a : loaded) {
agathions.put(a.npcId(), a);
itemIdToNpcId.put(a.itemId(), a.npcId());
}
}
} catch (FileNotFoundException ex) {
LOG.warn("data/stats/agathions.json not found!", ex);
} catch (IOException ex) {
LOG.warn("Failed to load agathions.json: ", ex);
}
}
public Agathion getByNpcId(int npcId) {
return agathions.get(npcId);
}
public Agathion getByItemId(int itemId) {
final var npcId = itemIdToNpcId.get(itemId);
return (npcId != null) ? agathions.get(npcId) : null;
}
public static AgathionRepository getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
static final AgathionRepository INSTANCE = new AgathionRepository();
}
}
@@ -0,0 +1,717 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.GameTimeController;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
import com.l2jserver.gameserver.network.serverpackets.AutoAttackStart;
import com.l2jserver.gameserver.network.serverpackets.AutoAttackStop;
import com.l2jserver.gameserver.network.serverpackets.Die;
import com.l2jserver.gameserver.network.serverpackets.MoveToLocation;
import com.l2jserver.gameserver.network.serverpackets.MoveToPawn;
import com.l2jserver.gameserver.network.serverpackets.StopMove;
import com.l2jserver.gameserver.network.serverpackets.StopRotation;
import com.l2jserver.gameserver.taskmanager.AttackStanceTaskManager;
/**
* Mother class of all objects AI in the world.<br>
* AbstractAI :<br>
* <li>L2CharacterAI</li>
*/
public abstract class AbstractAI implements Ctrl {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractAI.class);
private NextAction _nextAction;
public NextAction getNextAction() {
return _nextAction;
}
public void setNextAction(NextAction nextAction) {
_nextAction = nextAction;
}
private class FollowTask implements Runnable {
protected int _range = 70;
public FollowTask() {
}
public FollowTask(int range) {
_range = range;
}
@Override
public void run() {
try {
if (_followTask == null) {
return;
}
L2Character followTarget = _followTarget; // copy to prevent NPE
if (followTarget == null) {
if (_actor instanceof L2Summon) {
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
if (!_actor.isInsideRadius(followTarget, _range, true, false)) {
if (!_actor.isInsideRadius(followTarget, 3000, true, false)) {
// if the target is too far (maybe also teleported)
if (_actor instanceof L2Summon) {
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
moveToPawn(followTarget, _range);
}
} catch (Exception e) {
LOG.warn("{}: There has been a problem running the follow task!", getClass().getSimpleName(), e);
}
}
}
/** The character that this AI manages */
protected final L2Character _actor;
/** Current long-term intention */
protected CtrlIntention _intention = AI_INTENTION_IDLE;
/** Current long-term intention parameter */
protected Object _intentionArg0 = null;
/** Current long-term intention parameter */
protected Object _intentionArg1 = null;
/** Flags about client's state, in order to know which messages to send */
protected volatile boolean _clientMoving;
/** Flags about client's state, in order to know which messages to send */
protected volatile boolean _clientAutoAttacking;
/** Flags about client's state, in order to know which messages to send */
protected int _clientMovingToPawnOffset;
/** Different targets this AI maintains */
private L2Object _target;
private L2Character _castTarget;
protected L2Character _attackTarget;
protected L2Character _followTarget;
/** The skill we are currently casting by INTENTION_CAST */
protected Skill _skill;
/** Different internal state flags */
private int _moveToPawnTimeout;
protected Future<?> _followTask = null;
private static final int FOLLOW_INTERVAL = 1000;
private static final int ATTACK_FOLLOW_INTERVAL = 500;
/**
* Constructor of AbstractAI.
* @param creature the creature
*/
protected AbstractAI(L2Character creature) {
_actor = creature;
}
/**
* @return the L2Character managed by this Accessor AI.
*/
@Override
public L2Character getActor() {
return _actor;
}
/**
* @return the current Intention.
*/
@Override
public CtrlIntention getIntention() {
return _intention;
}
protected void setCastTarget(L2Character target) {
_castTarget = target;
}
/**
* @return the current cast target.
*/
public L2Character getCastTarget() {
return _castTarget;
}
protected void setAttackTarget(L2Character target) {
_attackTarget = target;
}
/**
* @return current attack target.
*/
@Override
public L2Character getAttackTarget() {
return _attackTarget;
}
/**
* Set the Intention of this AbstractAI.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is USED by AI classes</B></FONT><B><U><br>
* Overridden in </U> : </B><BR>
* <B>L2AttackableAI</B> : Create an AI Task executed every 1s (if necessary)<BR>
* <B>L2PlayerAI</B> : Stores the current AI intention parameters to later restore it if necessary.
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1) {
_intention = intention;
_intentionArg0 = arg0;
_intentionArg1 = arg1;
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
* @param intention The new Intention to set to the AI
*/
@Override
public final void setIntention(CtrlIntention intention) {
setIntention(intention, null, null);
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention (optional target)
*/
@Override
public final void setIntention(CtrlIntention intention, Object arg0) {
setIntention(intention, arg0, null);
}
@Override
public final void setIntention(CtrlIntention intention, Object arg0, Object arg1) {
// Stop the follow mode if necessary
if ((intention != AI_INTENTION_FOLLOW) && (intention != AI_INTENTION_ATTACK)) {
stopFollow();
}
// Launch the onIntention method of the L2CharacterAI corresponding to the new Intention
switch (intention) {
case AI_INTENTION_IDLE -> onIntentionIdle();
case AI_INTENTION_ACTIVE -> onIntentionActive();
case AI_INTENTION_REST -> onIntentionRest();
case AI_INTENTION_ATTACK -> onIntentionAttack((L2Character) arg0);
case AI_INTENTION_CAST -> onIntentionCast((Skill) arg0, (L2Object) arg1);
case AI_INTENTION_MOVE_TO -> onIntentionMoveTo((Location) arg0);
case AI_INTENTION_FOLLOW -> onIntentionFollow((L2Character) arg0);
case AI_INTENTION_PICK_UP -> onIntentionPickUp((L2Object) arg0);
case AI_INTENTION_INTERACT -> onIntentionInteract((L2Object) arg0);
}
// If do move or follow intention drop next action.
if ((_nextAction != null) && _nextAction.getIntentions().contains(intention)) {
_nextAction = null;
}
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
*/
@Override
public final void notifyEvent(CtrlEvent evt) {
notifyEvent(evt, null, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event. <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
* @param arg0 The first parameter of the Event (optional target)
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object arg0) {
notifyEvent(evt, arg0, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event. <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned period)</B></FONT>
* @param evt The event whose the AI must be notified
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object... args) {
if ((!_actor.isVisible() && !_actor.isTeleporting()) || !_actor.hasAI()) {
return;
}
switch (evt) {
case EVT_THINK:
onEvtThink();
break;
case EVT_ATTACKED:
onEvtAttacked((L2Character) args[0]);
break;
case EVT_AGGRESSION:
onEvtAggression((L2Character) args[0], ((Number) args[1]).intValue());
break;
case EVT_STUNNED:
onEvtStunned((L2Character) args[0]);
break;
case EVT_SLEEPING:
onEvtSleeping((L2Character) args[0]);
break;
case EVT_ROOTED:
onEvtRooted((L2Character) args[0]);
break;
case EVT_CONFUSED:
onEvtConfused((L2Character) args[0]);
break;
case EVT_MUTED:
onEvtMuted((L2Character) args[0]);
break;
case EVT_EVADED:
onEvtEvaded((L2Character) args[0]);
break;
case EVT_READY_TO_ACT:
if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow()) {
onEvtReadyToAct();
}
break;
case EVT_USER_CMD:
onEvtUserCmd(args[0], args[1]);
break;
case EVT_ARRIVED:
// happens e.g. from stopmove but we don't process it if we're casting
if (!_actor.isCastingNow() && !_actor.isCastingSimultaneouslyNow()) {
onEvtArrived();
}
break;
case EVT_ARRIVED_REVALIDATE:
// this is disregarded if the char is not moving any more
if (_actor.isMoving()) {
onEvtArrivedRevalidate();
}
break;
case EVT_ARRIVED_BLOCKED:
onEvtArrivedBlocked((Location) args[0]);
break;
case EVT_FORGET_OBJECT:
onEvtForgetObject((L2Object) args[0]);
break;
case EVT_CANCEL:
onEvtCancel();
break;
case EVT_DEAD:
onEvtDead();
break;
case EVT_FAKE_DEATH:
onEvtFakeDeath();
break;
case EVT_FINISH_CASTING:
onEvtFinishCasting();
break;
case EVT_AFRAID: {
onEvtAfraid((L2Character) args[0], (Boolean) args[1]);
break;
}
}
// Do next action.
if ((_nextAction != null) && _nextAction.getEvents().contains(evt)) {
_nextAction.doAction();
}
}
protected abstract void onIntentionIdle();
protected abstract void onIntentionActive();
protected abstract void onIntentionRest();
protected abstract void onIntentionAttack(L2Character target);
protected abstract void onIntentionCast(Skill skill, L2Object target);
protected abstract void onIntentionMoveTo(Location destination);
protected abstract void onIntentionFollow(L2Character target);
protected abstract void onIntentionPickUp(L2Object item);
protected abstract void onIntentionInteract(L2Object object);
protected abstract void onEvtThink();
protected abstract void onEvtAttacked(L2Character attacker);
protected abstract void onEvtAggression(L2Character target, long aggro);
protected abstract void onEvtStunned(L2Character attacker);
protected abstract void onEvtSleeping(L2Character attacker);
protected abstract void onEvtRooted(L2Character attacker);
protected abstract void onEvtConfused(L2Character attacker);
protected abstract void onEvtMuted(L2Character attacker);
protected abstract void onEvtEvaded(L2Character attacker);
protected abstract void onEvtReadyToAct();
protected abstract void onEvtUserCmd(Object arg0, Object arg1);
protected abstract void onEvtArrived();
protected abstract void onEvtArrivedRevalidate();
protected abstract void onEvtArrivedBlocked(Location blocked_at_pos);
protected abstract void onEvtForgetObject(L2Object object);
protected abstract void onEvtCancel();
protected abstract void onEvtDead();
protected abstract void onEvtFakeDeath();
protected abstract void onEvtFinishCasting();
protected abstract void onEvtAfraid(L2Character effector, boolean start);
/**
* Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor. <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
protected void clientActionFailed() {
if (_actor instanceof L2PcInstance) {
_actor.sendPacket(ActionFailed.STATIC_PACKET);
}
}
/**
* Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param pawn
* @param offset
*/
protected void moveToPawn(L2Object pawn, int offset) {
// Check if actor can move
if (!_actor.isMovementDisabled()) {
if (offset < 10) {
offset = 10;
}
// prevent possible extra calls to this function (there is none?),
// also don't send movetopawn packets too often
boolean sendPacket = true;
if (_clientMoving && (_target == pawn)) {
if (_clientMovingToPawnOffset == offset) {
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout) {
return;
}
sendPacket = false;
} else if (_actor.isOnGeodataPath()) {
// minimum time to calculate new route is 2 seconds
if (GameTimeController.getInstance().getGameTicks() < (_moveToPawnTimeout + 10)) {
return;
}
}
}
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = offset;
_target = pawn;
_moveToPawnTimeout = GameTimeController.getInstance().getGameTicks();
_moveToPawnTimeout += 1000 / GameTimeController.MILLIS_IN_TICK;
if (pawn == null) {
return;
}
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_actor.moveToLocation(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
if (!_actor.isMoving()) {
clientActionFailed();
return;
}
// Send a Server->Client packet MoveToPawn/CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
if (pawn instanceof L2Character) {
if (_actor.isOnGeodataPath()) {
_actor.broadcastPacket(new MoveToLocation(_actor));
_clientMovingToPawnOffset = 0;
} else if (sendPacket) {
_actor.broadcastPacket(new MoveToPawn(_actor, (L2Character) pawn, offset));
}
} else {
_actor.broadcastPacket(new MoveToLocation(_actor));
}
} else {
clientActionFailed();
}
}
/**
* Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param x
* @param y
* @param z
*/
protected void moveTo(int x, int y, int z) {
// Chek if actor can move
if (!_actor.isMovementDisabled()) {
// Set AI movement data
_clientMoving = true;
_clientMovingToPawnOffset = 0;
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_actor.moveToLocation(x, y, z, 0);
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new MoveToLocation(_actor));
} else {
clientActionFailed();
}
}
/**
* Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param loc
*/
protected void clientStopMoving(Location loc) {
// Stop movement of the L2Character
if (_actor.isMoving()) {
_actor.stopMove(loc);
}
_clientMovingToPawnOffset = 0;
if (_clientMoving || (loc != null)) {
_clientMoving = false;
// Send a Server->Client packet StopMove to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new StopMove(_actor));
if (loc != null) {
// Send a Server->Client packet StopRotation to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new StopRotation(_actor.getObjectId(), loc.getHeading(), 0));
}
}
}
/**
* Client has already arrived to target, no need to force StopMove packet.
*/
protected void clientStoppedMoving() {
if (_clientMovingToPawnOffset > 0) // movetoPawn needs to be stopped
{
_clientMovingToPawnOffset = 0;
_actor.broadcastPacket(new StopMove(_actor));
}
_clientMoving = false;
}
public boolean isAutoAttacking() {
return _clientAutoAttacking;
}
public void setAutoAttacking(boolean isAutoAttacking) {
if (_actor instanceof L2Summon summon) {
if (summon.getOwner() != null) {
summon.getOwner().getAI().setAutoAttacking(isAutoAttacking);
}
return;
}
_clientAutoAttacking = isAutoAttacking;
}
/**
* Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
public void clientStartAutoAttack() {
if (_actor instanceof L2Summon summon) {
if (summon.getOwner() != null) {
summon.getOwner().getAI().clientStartAutoAttack();
}
return;
}
if (!isAutoAttacking()) {
if (_actor.isPlayer() && _actor.hasSummon()) {
_actor.getSummon().broadcastPacket(new AutoAttackStart(_actor.getSummon().getObjectId()));
}
// Send a Server->Client packet AutoAttackStart to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new AutoAttackStart(_actor.getObjectId()));
setAutoAttacking(true);
}
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
/**
* Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
public void clientStopAutoAttack() {
if (_actor instanceof L2Summon summon) {
if (summon.getOwner() != null) {
summon.getOwner().getAI().clientStopAutoAttack();
}
return;
}
if (_actor instanceof L2PcInstance) {
if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_actor) && isAutoAttacking()) {
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
} else if (isAutoAttacking()) {
_actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId()));
setAutoAttacking(false);
}
}
/**
* Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die <I>(broadcast)</I>.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
*/
protected void clientNotifyDead() {
// Send a Server->Client packet Die to the actor and all L2PcInstance in its _knownPlayers
Die msg = new Die(_actor);
_actor.broadcastPacket(msg);
// Init AI
_intention = AI_INTENTION_IDLE;
_target = null;
_castTarget = null;
_attackTarget = null;
// Cancel the follow task if necessary
stopFollow();
}
/**
* Update the state of this actor client side by sending Server->Client packet MoveToPawn/CharMoveToLocation and AutoAttackStart to the L2PcInstance player.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
* @param player The L2PcInstance to notify with state of this L2Character
*/
public void describeStateToPlayer(L2PcInstance player) {
if (getActor().isVisibleFor(player)) {
if (_clientMoving) {
if ((_clientMovingToPawnOffset != 0) && (_followTarget != null)) {
// Send a Server->Client packet MoveToPawn to the actor and all L2PcInstance in its _knownPlayers
player.sendPacket(new MoveToPawn(_actor, _followTarget, _clientMovingToPawnOffset));
} else {
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
player.sendPacket(new MoveToLocation(_actor));
}
}
}
}
/**
* Create and Launch an AI Follow Task to execute every 1s.
* @param target The L2Character to follow
*/
public synchronized void startFollow(L2Character target) {
if (_followTask != null) {
_followTask.cancel(false);
_followTask = null;
}
// Create and Launch an AI Follow Task to execute every 1s
_followTarget = target;
_followTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(), 5, FOLLOW_INTERVAL);
}
/**
* Create and Launch an AI Follow Task to execute every 0.5s, following at specified range.
* @param target The L2Character to follow
* @param range
*/
public synchronized void startFollow(L2Character target, int range) {
if (_followTask != null) {
_followTask.cancel(false);
_followTask = null;
}
_followTarget = target;
_followTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(range), 5, ATTACK_FOLLOW_INTERVAL);
}
/**
* Stop an AI Follow Task.
*/
public synchronized void stopFollow() {
if (_followTask != null) {
// Stop the Follow Task
_followTask.cancel(false);
_followTask = null;
}
_followTarget = null;
}
protected L2Character getFollowTarget() {
return _followTarget;
}
protected L2Object getTarget() {
return _target;
}
protected void setTarget(L2Object target) {
_target = target;
}
/**
* Stop all Ai tasks and futures.
*/
public void stopAITask() {
stopFollow();
}
@Override
public String toString() {
return "Actor: " + _actor;
}
}
@@ -0,0 +1,104 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.actor.L2Character;
/**
* Interface of AI and client state.<br>
* To correctly send messages to client we need it's state.<br>
* For example, if we've sent 'StartAutoAttack' message, we need to send 'StopAutoAttack' message before any other action.<br>
* Or if we've sent 'MoveToPawn', we need to send 'StopMove' when the movement of a character is canceled (by Root spell or any other reason).<br>
* Thus, we need to know the state of client, i.e. which messages we've sent and how the client will show the scene.<br>
* Close to this task is the task of AI.<br>
* If a player's character is attacking a mob, his ATTACK may be interrupted by an event, that temporary disables attacking.<br>
* But when the possibility to ATTACK will be enabled, the character must continue the ATTACK.<br>
* For mobs, it may be more complex, since we want them to decide when to use magic, or when to follow the player for physical combat, or when to escape, to help another mob, etc.<br>
* This interface is hiding complexity of server<->client interaction and multiple states of a character.<br>
* It allows to set a desired, simple "wish" of a character, and the implementation of this interface will take care about the rest.<br>
* The goal of a character may be like "ATTACK", "random walk" and so on.<br>
* To reach the goal implementation will split it into several small actions, several steps (possibly repeatable).<br>
* Like "run to target" then "hit it", then if target is not dead - repeat.<br>
* This flow of simpler steps may be interrupted by incoming events.<br>
* Like a character's movement was disabled (by Root spell, for instance).<br>
* Depending on character's ability AI may choose to wait, or to use magic ATTACK and so on.<br>
* Additionally, incoming events are compared with client's state of the character,<br>
* and required network messages are sent to client's, i.e. if we have incoming event that character's movement was disabled, it causes changing if its behavior,<br>
* and if client's state for the character is "moving" we send messages to clients to stop the avatar/mob.
*/
public interface Ctrl {
/**
* Gets the actor.
* @return the actor
*/
L2Character getActor();
/**
* Gets the intention.
* @return the intention
*/
CtrlIntention getIntention();
/**
* Gets the attack target.
* @return the attack target
*/
L2Character getAttackTarget();
/**
* Set general state/intention for AI, with optional data.
* @param intention the new intention
*/
void setIntention(CtrlIntention intention);
/**
* Sets the intention.
* @param intention the intention
* @param arg0 the arg0
*/
void setIntention(CtrlIntention intention, Object arg0);
/**
* Sets the intention.
* @param intention the intention
* @param arg0 the arg0
* @param arg1 the arg1
*/
void setIntention(CtrlIntention intention, Object arg0, Object arg1);
/**
* Event, that notifies about previous step result, or user command, that does not change current general intention.
* @param evt the event
*/
void notifyEvent(CtrlEvent evt);
/**
* Notify an event.
* @param evt the event
* @param arg0 the arg0
*/
void notifyEvent(CtrlEvent evt, Object arg0);
/**
* Notify an event.
* @param evt the event
* @param args the args
*/
void notifyEvent(CtrlEvent evt, Object... args);
}
@@ -0,0 +1,87 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
/**
* This class contains an enum of each possible event that can happen on an AI character.
*/
public enum CtrlEvent {
/**
* Something has changed, usually a previous step has being completed or maybe was completed, the AI must think on next action.
*/
EVT_THINK,
/**
* The actor was attacked. This event comes each time a physical or magical<br>
* attack was done on the actor. NPC may start attack in response, or ignore<br>
* this event if they already attack someone, or change target and so on.
*/
EVT_ATTACKED,
/** Increase/decrease aggression towards a target, or reduce global aggression if target is null */
EVT_AGGRESSION,
/** Actor is in stun state */
EVT_STUNNED,
/** Actor starts/stops sleeping */
EVT_SLEEPING,
/** Actor is in rooted state (cannot move) */
EVT_ROOTED,
/** Actor evaded hit **/
EVT_EVADED,
/**
* An event that previous action was completed. The action may be an attempt to physically/magically hit an enemy, or an action that discarded attack attempt has finished.
*/
EVT_READY_TO_ACT,
/**
* User's command, like using a combat magic or changing weapon, etc. The command is not intended to change final goal
*/
EVT_USER_CMD,
/**
* The actor arrived to assigned location, or it's a time to modify movement destination (follow, interact, random move and others intentions).
*/
EVT_ARRIVED,
/**
* The actor arrived to an intermediate point, and needs to revalidate destination. This is sent when follow/move to pawn if destination is far away.
*/
EVT_ARRIVED_REVALIDATE,
/** The actor cannot move anymore. */
EVT_ARRIVED_BLOCKED,
/** Forgets an object (if it's used as attack target, follow target and so on */
EVT_FORGET_OBJECT,
/**
* Attempt to cancel current step execution, but not change the intention.<br>
* For example, the actor was put into a stun, so it's current attack<br>
* or movement has to be canceled. But after the stun state expired,<br>
* the actor may try to attack again. Another usage for CANCEL is a user's<br>
* attempt to cancel a cast/bow attack and so on.
*/
EVT_CANCEL,
/** The character is dead */
EVT_DEAD,
/** The character looks like dead */
EVT_FAKE_DEATH,
/** The character attack anyone randomly **/
EVT_CONFUSED,
/** The character cannot cast spells anymore **/
EVT_MUTED,
/** The character flee in random directions **/
EVT_AFRAID,
/** The character finish casting **/
EVT_FINISH_CASTING,
/** The character betrayed its master */
EVT_BETRAYED
}
@@ -0,0 +1,43 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
/**
* Enumeration of generic intentions of an NPC/PC, an intention may require several steps to be completed.
*/
public enum CtrlIntention {
/** Do nothing, disconnect AI of NPC if no players around */
AI_INTENTION_IDLE,
/** Alerted state without goal : scan attackable targets, random walk, etc */
AI_INTENTION_ACTIVE,
/** Rest (sit until attacked) */
AI_INTENTION_REST,
/** Attack target (cast combat magic, go to target, combat), may be ignored, if target is locked on another character or a peaceful zone and so on. */
AI_INTENTION_ATTACK,
/** Cast a spell, depending on the spell - may start or stop attacking */
AI_INTENTION_CAST,
/** Just move to another location */
AI_INTENTION_MOVE_TO,
/** Like move, but check target's movement and follow it */
AI_INTENTION_FOLLOW,
/** PickUp and item, (got to item, pickup it, become idle */
AI_INTENTION_PICK_UP,
/** Move to target, then interact */
AI_INTENTION_INTERACT
}
@@ -0,0 +1,50 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.actor.L2Character;
/**
* Fear task.
* @author Zoey76
* @version 2.6.3.0
*/
public class FearTask implements Runnable {
protected static final int FEAR_TICKS = 5;
private final L2AttackableAI ai;
private final L2Character effector;
private boolean start;
public FearTask(L2AttackableAI ai, L2Character effector, boolean start) {
this.ai = ai;
this.effector = effector;
this.start = start;
}
@Override
public void run() {
ai.setFearTime(ai.getFearTime() - FEAR_TICKS);
ai.onEvtAfraid(effector, start);
start = false;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.actor.instance.L2AirShipInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.network.serverpackets.ExMoveToLocationAirShip;
import com.l2jserver.gameserver.network.serverpackets.ExStopMoveAirShip;
/**
* @author DS
*/
public class L2AirShipAI extends L2VehicleAI {
public L2AirShipAI(L2AirShipInstance creature) {
super(creature);
}
@Override
protected void moveTo(int x, int y, int z) {
if (!_actor.isMovementDisabled()) {
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new ExMoveToLocationAirShip(getActor()));
}
}
@Override
protected void clientStopMoving(Location loc) {
if (_actor.isMoving()) {
_actor.stopMove(loc);
}
if (_clientMoving || (loc != null)) {
_clientMoving = false;
_actor.broadcastPacket(new ExStopMoveAirShip(getActor()));
}
}
@Override
public void describeStateToPlayer(L2PcInstance player) {
if (_clientMoving) {
player.sendPacket(new ExMoveToLocationAirShip(getActor()));
}
}
@Override
public L2AirShipInstance getActor() {
return (L2AirShipInstance) _actor;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,73 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.actor.instance.L2BoatInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.network.serverpackets.VehicleDeparture;
import com.l2jserver.gameserver.network.serverpackets.VehicleInfo;
import com.l2jserver.gameserver.network.serverpackets.VehicleStarted;
/**
* @author DS
*/
public class L2BoatAI extends L2VehicleAI {
public L2BoatAI(L2BoatInstance creature) {
super(creature);
}
@Override
protected void moveTo(int x, int y, int z) {
if (!_actor.isMovementDisabled()) {
if (!_clientMoving) {
_actor.broadcastPacket(new VehicleStarted(getActor(), 1));
}
_clientMoving = true;
_actor.moveToLocation(x, y, z, 0);
_actor.broadcastPacket(new VehicleDeparture(getActor()));
}
}
@Override
protected void clientStopMoving(Location loc) {
if (_actor.isMoving()) {
_actor.stopMove(loc);
}
if (_clientMoving || (loc != null)) {
_clientMoving = false;
_actor.broadcastPacket(new VehicleStarted(getActor(), 0));
_actor.broadcastPacket(new VehicleInfo(getActor()));
}
}
@Override
public void describeStateToPlayer(L2PcInstance player) {
if (_clientMoving) {
player.sendPacket(new VehicleDeparture(getActor()));
}
}
@Override
public L2BoatInstance getActor() {
return (L2BoatInstance) _actor;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,437 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import java.util.Collection;
import java.util.stream.Collectors;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.MobGroup;
import com.l2jserver.gameserver.model.MobGroupTable;
import com.l2jserver.gameserver.model.actor.L2Attackable;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.actor.instance.L2ControllableMobInstance;
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.util.Util;
/**
* AI for controllable mobs
* @author littlecrow
*/
public final class L2ControllableMobAI extends L2AttackableAI {
public static final int AI_IDLE = 1;
public static final int AI_NORMAL = 2;
public static final int AI_FORCE_ATTACK = 3;
public static final int AI_FOLLOW = 4;
public static final int AI_CAST = 5;
public static final int AI_ATTACK_GROUP = 6;
private int _alternateAI;
private boolean _isThinking; // to prevent thinking recursively
private boolean _isNotMoving;
private L2Character _forcedTarget;
private MobGroup _targetGroup;
public L2ControllableMobAI(L2ControllableMobInstance creature) {
super(creature);
setAlternateAI(AI_IDLE);
}
protected void thinkFollow() {
L2Attackable me = (L2Attackable) _actor;
if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true)) {
int signX = (Rnd.nextInt(2) == 0) ? -1 : 1;
int signY = (Rnd.nextInt(2) == 0) ? -1 : 1;
int randX = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
int randY = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
moveTo(getForcedTarget().getX() + (signX * randX), getForcedTarget().getY() + (signY * randY), getForcedTarget().getZ());
}
}
@Override
protected void onEvtThink() {
if (_isThinking) {
return;
}
_isThinking = true;
try {
switch (getAlternateAI()) {
case AI_IDLE:
if (getIntention() != CtrlIntention.AI_INTENTION_ACTIVE) {
setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
}
break;
case AI_FOLLOW:
thinkFollow();
break;
case AI_CAST:
thinkCast();
break;
case AI_FORCE_ATTACK:
thinkForceAttack();
break;
case AI_ATTACK_GROUP:
thinkAttackGroup();
break;
default:
if (getIntention() == AI_INTENTION_ACTIVE) {
thinkActive();
} else if (getIntention() == AI_INTENTION_ATTACK) {
thinkAttack();
}
break;
}
} finally {
_isThinking = false;
}
}
@Override
protected void thinkCast() {
final var attackTarget = getAttackTarget();
if ((attackTarget == null) || attackTarget.isAlikeDead()) {
setAttackTarget(findNextRndTarget());
clientStopMoving(null);
if (attackTarget == null) {
return;
}
}
final var actor = getActor();
actor.setTarget(attackTarget);
if (!_actor.isMuted()) {
int max_range = 0;
// check distant skills
for (Skill sk : _actor.getAllSkills()) {
if (Util.checkIfInRange(sk.getCastRange(), _actor, attackTarget, true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, sk.getCastRange());
}
if (!isNotMoving()) {
moveToPawn(attackTarget, max_range);
}
}
}
protected void thinkAttackGroup() {
L2Character target = getForcedTarget();
if ((target == null) || target.isAlikeDead()) {
// try to get next group target
setForcedTarget(findNextGroupTarget());
clientStopMoving(null);
}
if (target == null) {
return;
}
_actor.setTarget(target);
// as a response, we put the target in a forced attack mode
L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
ctrlAi.forceAttack(_actor);
double dist2 = _actor.calculateDistance(target, false, true);
int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
// check distant skills
for (Skill sk : _actor.getAllSkills()) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
if (!isNotMoving()) {
moveToPawn(target, range);
}
return;
}
_actor.doAttack(target);
}
protected void thinkForceAttack() {
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead()) {
clientStopMoving(null);
setIntention(AI_INTENTION_ACTIVE);
setAlternateAI(AI_IDLE);
}
_actor.setTarget(getForcedTarget());
double dist2 = _actor.calculateDistance(getForcedTarget(), false, true);
int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
// check distant skills
for (Skill sk : _actor.getAllSkills()) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
if (!isNotMoving()) {
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
}
return;
}
_actor.doAttack(getForcedTarget());
}
@Override
protected void thinkAttack() {
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead()) {
if (getAttackTarget() != null) {
// stop hating
L2Attackable npc = (L2Attackable) _actor;
npc.stopHating(getAttackTarget());
}
setIntention(AI_INTENTION_ACTIVE);
} else {
// notify aggression
if (((L2Npc) _actor).getTemplate().getClans() != null) {
Collection<L2Object> objs = _actor.getKnownList().getKnownObjects().values();
for (L2Object obj : objs) {
if (!(obj instanceof L2Npc npc)) {
continue;
}
if (!npc.isInMyClan((L2Npc) _actor)) {
continue;
}
if (_actor.isInsideRadius(npc, npc.getTemplate().getClanHelpRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200)) {
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
}
}
_actor.setTarget(getAttackTarget());
double dist2 = _actor.calculateDistance(getAttackTarget(), false, true);
int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
int max_range = range;
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
// check distant skills
for (Skill sk : _actor.getAllSkills()) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
_actor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
moveToPawn(getAttackTarget(), range);
return;
}
// Force mobs to attack anybody if confused.
L2Character hated;
if (_actor.isConfused()) {
hated = findNextRndTarget();
} else {
hated = getAttackTarget();
}
if (hated == null) {
setIntention(AI_INTENTION_ACTIVE);
return;
}
if (hated != getAttackTarget()) {
setAttackTarget(hated);
}
if (!_actor.isMuted() && (Rnd.nextInt(5) == 3)) {
for (Skill sk : _actor.getAllSkills()) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume2(sk))) {
_actor.doCast(sk);
return;
}
}
}
_actor.doAttack(getAttackTarget());
}
}
@Override
protected void thinkActive() {
setAttackTarget(findNextRndTarget());
L2Character hated;
if (_actor.isConfused()) {
hated = findNextRndTarget();
} else {
hated = getAttackTarget();
}
if (hated != null) {
_actor.setRunning();
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
}
}
private boolean checkAutoAttackCondition(L2Character target) {
if ((target == null) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance)) {
return false;
}
// TODO(Zoey76)[#112]: This check must change if summon fall in L2Npc hierarchy.
if (target.isNpc()) {
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul() || target.isAlikeDead()) {
return false;
}
// Spawn protection (only against mobs)
if (target.isPlayer() && ((L2PcInstance) target).isSpawnProtected()) {
return false;
}
final var actor = getActor();
if (!_actor.isInsideRadius(target, actor.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100)) {
return false;
}
// Check if the target is a L2Playable
if (target.isPlayable()) {
// Check if the target isn't in silent move mode
if (((L2Playable) target).isSilentMovingAffected()) {
return false;
}
}
return actor.isAggressive();
}
private L2Character findNextRndTarget() {
final var potentialTarget = _actor.getKnownList().getKnownCharactersInRadius(getActor().getAggroRange()).stream().filter(this::checkAutoAttackCondition).collect(Collectors.toList());
if (potentialTarget.isEmpty()) {
return null;
}
return potentialTarget.get(Rnd.nextInt(potentialTarget.size()));
}
private L2ControllableMobInstance findNextGroupTarget() {
return getGroupTarget().getRandomMob();
}
public int getAlternateAI() {
return _alternateAI;
}
public void setAlternateAI(int alternateAI) {
_alternateAI = alternateAI;
}
public void forceAttack(L2Character target) {
setAlternateAI(AI_FORCE_ATTACK);
setForcedTarget(target);
}
public void forceAttackGroup(MobGroup group) {
setForcedTarget(null);
setGroupTarget(group);
setAlternateAI(AI_ATTACK_GROUP);
}
public void stop() {
setAlternateAI(AI_IDLE);
clientStopMoving(null);
}
public void move(int x, int y, int z) {
moveTo(x, y, z);
}
public void follow(L2Character target) {
setAlternateAI(AI_FOLLOW);
setForcedTarget(target);
}
public boolean isNotMoving() {
return _isNotMoving;
}
public void setNotMoving(boolean isNotMoving) {
_isNotMoving = isNotMoving;
}
private L2Character getForcedTarget() {
return _forcedTarget;
}
private MobGroup getGroupTarget() {
return _targetGroup;
}
private void setForcedTarget(L2Character forcedTarget) {
_forcedTarget = forcedTarget;
}
private void setGroupTarget(MobGroup targetGroup) {
_targetGroup = targetGroup;
}
}
@@ -0,0 +1,149 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jserver.gameserver.model.skills.Skill;
/**
* @author mkizub
*/
public class L2DoorAI extends L2CharacterAI {
public L2DoorAI(L2DoorInstance creature) {
super(creature);
}
@Override
protected void onIntentionIdle() {
}
@Override
protected void onIntentionActive() {
}
@Override
protected void onIntentionRest() {
}
@Override
protected void onIntentionAttack(L2Character target) {
}
@Override
protected void onIntentionCast(Skill skill, L2Object target) {
}
@Override
protected void onIntentionMoveTo(Location destination) {
}
@Override
protected void onIntentionFollow(L2Character target) {
}
@Override
protected void onIntentionPickUp(L2Object item) {
}
@Override
protected void onIntentionInteract(L2Object object) {
}
@Override
protected void onEvtThink() {
}
@Override
protected void onEvtAttacked(L2Character attacker) {
ThreadPoolManager.getInstance().executeGeneral(new onEventAttackedDoorTask((L2DoorInstance) _actor, attacker));
}
@Override
protected void onEvtAggression(L2Character target, long aggro) {
}
@Override
protected void onEvtStunned(L2Character attacker) {
}
@Override
protected void onEvtSleeping(L2Character attacker) {
}
@Override
protected void onEvtRooted(L2Character attacker) {
}
@Override
protected void onEvtReadyToAct() {
}
@Override
protected void onEvtUserCmd(Object arg0, Object arg1) {
}
@Override
protected void onEvtArrived() {
}
@Override
protected void onEvtArrivedRevalidate() {
}
@Override
protected void onEvtArrivedBlocked(Location blocked_at_loc) {
}
@Override
protected void onEvtForgetObject(L2Object object) {
}
@Override
protected void onEvtCancel() {
}
@Override
protected void onEvtDead() {
}
private class onEventAttackedDoorTask implements Runnable {
private final L2DoorInstance _door;
private final L2Character _attacker;
public onEventAttackedDoorTask(L2DoorInstance door, L2Character attacker) {
_door = door;
_attacker = attacker;
}
@Override
public void run() {
for (L2DefenderInstance guard : _door.getKnownDefenders()) {
if (_actor.isInsideRadius(guard, guard.getTemplate().getClanHelpRange(), false, true) && (Math.abs(_attacker.getZ() - guard.getZ()) < 200)) {
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
}
}
}
}
@@ -0,0 +1,806 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.Collection;
import java.util.concurrent.Future;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.GameTimeController;
import com.l2jserver.gameserver.GeoData;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Attackable;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jserver.gameserver.model.actor.instance.L2FortCommanderInstance;
import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.effects.L2EffectType;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.util.Util;
/**
* Fort siege guards AI.
*/
public class L2FortSiegeGuardAI extends L2CharacterAI implements Runnable {
private static final int MAX_ATTACK_TIMEOUT = 30 * GameTimeController.TICKS_PER_SECOND; // 30s converted to ticks
/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** For attack AI, analysis of mob and its targets */
private final SelfAnalysis _selfAnalysis = new SelfAnalysis();
/** The delay after which the attacked is stopped */
private int _attackTimeout;
/** The L2Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of L2AttackableAI.
* @param creature the creature
*/
public L2FortSiegeGuardAI(L2DefenderInstance creature) {
super(creature);
_selfAnalysis.init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = _actor.getPhysicalAttackRange();
}
@Override
public void run() {
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* <B><U> Actor is a L2GuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* <li>The L2MonsterInstance target is aggressive</li>
* </ul>
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The L2PcInstance target isn't a Defender</li>
* </ul>
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* </ul>
* <B><U> Actor is a L2MonsterInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li>
* </ul>
* @param target The targeted L2Object
* @return True if the target is autoattackable (depends on the actor type).
*/
private boolean autoAttackCondition(L2Character target) {
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof L2DefenderInstance) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance) || target.isAlikeDead() || (target instanceof L2FortCommanderInstance) || (target instanceof L2Playable)) {
L2PcInstance player = null;
if (target instanceof L2PcInstance) {
player = ((L2PcInstance) target);
} else if (target instanceof L2Summon) {
player = ((L2Summon) target).getOwner();
}
if ((player == null) || ((player.getClan() != null) && (player.getClan().getFortId() == ((L2Npc) _actor).getFort().getResidenceId()))) {
return false;
}
}
// Check if the target isn't invulnerable
if (target.isInvul()) {
// However EffectInvincible requires to check GMs specially
if (target.isPlayer() && target.isGM()) {
return false;
}
if (target.isSummon() && ((L2Summon) target).getOwner().isGM()) {
return false;
}
}
// Get the owner if the target is a summon
if (target instanceof L2Summon) {
L2PcInstance owner = ((L2Summon) target).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false)) {
target = owner;
}
}
// Check if the target is a L2PcInstance
if (target instanceof L2Playable playable) {
// Check if the target isn't in silent move mode AND too far (>100)
if (playable.isSilentMovingAffected() && !_actor.isInsideRadius(target, 250, false, false)) {
return false;
}
}
// Los Check Here
return (_actor.isAutoAttackable(target) && GeoData.getInstance().canSeeTarget(_actor, target));
}
/**
* Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1) {
LOG.debug("{}: changeIntention({}, {}, {})", getClass().getSimpleName(), intention, arg0, arg1);
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead()) {
L2Attackable npc = (L2Attackable) _actor;
// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
if (!npc.getKnownList().getKnownPlayers().isEmpty()) {
intention = AI_INTENTION_ACTIVE;
} else {
intention = AI_INTENTION_IDLE;
}
}
if (intention == AI_INTENTION_IDLE) {
// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null) {
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_actor.detachAI();
return;
}
}
// Set the Intention of this L2AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if ((_aiTask == null) && (_actor.isInActiveRegion())) {
_aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The L2Character to attack
*/
@Override
protected void onIntentionAttack(L2Character target) {
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
* </ul>
*/
private void thinkActive() {
L2Attackable npc = (L2Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0) {
if (_globalAggro < 0) {
_globalAggro++;
} else {
_globalAggro--;
}
}
// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0) {
for (L2Character target : npc.getKnownList().getKnownCharactersInRadius(_attackRange)) {
if (target == null) {
continue;
}
if (autoAttackCondition(target)) // check aggression
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
long hating = npc.getHating(target);
// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
if (hating == 0) {
npc.addDamageHate(target, 0, 1);
}
}
}
// Chose a target from its aggroList
L2Character hated;
if (_actor.isConfused()) {
hated = getAttackTarget(); // Force mobs to attack anybody if confused
} else {
hated = npc.getMostHated();
// _mostHatedAnalysis.Update(hated);
}
// Order to the L2Attackable to attack the target
if (hated != null) {
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
long aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0) {
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the L2SiegeGuardInstance to return to its home location because there's no target to attack
if (_actor.getWalkSpeed() >= 0) {
if (_actor instanceof L2DefenderInstance defender) {
defender.returnHome();
} else {
((L2FortCommanderInstance) _actor).returnHome();
}
}
}
/**
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all L2Object of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li>
* </ul>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack() {
LOG.debug("{}: thinkAttack(); timeout={}", getClass().getSimpleName(), (_attackTimeout - GameTimeController.getInstance().getGameTicks()));
if (_attackTimeout < GameTimeController.getInstance().getGameTicks()) {
// Check if the actor is running
if (_actor.isRunning()) {
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
}
}
L2Character attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks())) {
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null) {
L2Attackable npc = (L2Attackable) _actor;
npc.stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private void factionNotifyAndSupport() {
L2Character target = getAttackTarget();
// Call all L2Object of its Faction inside the Faction Range
if ((((L2Npc) _actor).getTemplate().getClans() == null) || (target == null)) {
return;
}
if (target.isInvul()) {
return; // speeding it up for siege guards
}
// Go through all L2Character that belong to its faction
// for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(((L2NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(1000)) {
if (cha == null) {
continue;
}
if (!(cha instanceof L2Npc npc)) {
if (_selfAnalysis.hasHealOrResurrect && (cha instanceof L2PcInstance) && ((L2Npc) _actor).getFort().getSiege().checkIsDefender(cha.getClan())) {
// heal friends
if (!_actor.isAttackingDisabled() && (cha.getCurrentHp() < (cha.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2.0)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2.0)) && cha.isInCombat()) {
for (Skill sk : _selfAnalysis.healSkills) {
if (_actor.getCurrentMp() < sk.getMpConsume2()) {
continue;
}
if (_actor.isSkillDisabled(sk)) {
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, cha, true)) {
continue;
}
int chance = 5;
if (chance >= Rnd.get(100)) {
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, cha)) {
break;
}
L2Object oldTarget = _actor.getTarget();
_actor.setTarget(cha);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(oldTarget);
return;
}
}
}
continue;
}
if (!npc.isInMyClan((L2Npc) _actor)) {
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600)
// && _actor.getAttackByList().contains(getAttackTarget())
&& ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE))
// limiting aggro for siege guards
&& target.isInsideRadius(npc, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target)) {
// Notify the L2Object AI with EVT_AGGRESSION
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
return;
}
// heal friends
if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2.0)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2.0)) && npc.isInCombat()) {
for (Skill sk : _selfAnalysis.healSkills) {
if (_actor.getCurrentMp() < sk.getMpConsume2()) {
continue;
}
if (_actor.isSkillDisabled(sk)) {
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true)) {
continue;
}
int chance = 4;
if (chance >= Rnd.get(100)) {
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, npc)) {
break;
}
L2Object OldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
}
}
private void attackPrepare() {
// Get all information needed to choose between physical or magical attack
Collection<Skill> skills;
double dist_2;
int range;
L2DefenderInstance sGuard;
if (_actor instanceof L2FortCommanderInstance) {
sGuard = (L2FortCommanderInstance) _actor;
} else {
sGuard = (L2DefenderInstance) _actor;
}
L2Character attackTarget = getAttackTarget();
try {
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist_2 = _actor.calculateDistance(attackTarget, false, true);
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
if (attackTarget.isMoving()) {
range += 50;
}
} catch (NullPointerException e) {
LOG.warn("{}: Attack target is NULL.", getClass().getSimpleName());
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((attackTarget instanceof L2PcInstance) && sGuard.getFort().getSiege().checkIsDefender(attackTarget.getClan())) {
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
if (!GeoData.getInstance().canSeeTarget(_actor, attackTarget)) {
// Siege guards differ from normal mobs currently:
// If target cannot seen, don't attack any more
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist_2 > (range * range))) {
// check for long ranged skills and heal/buff skills
for (Skill sk : skills) {
int castRange = sk.getCastRange();
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume2(sk)) && !sk.isPassive()) {
L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HP))) {
boolean useSkillSelf = true;
if ((sk.hasEffectType(L2EffectType.HP)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) {
useSkillSelf = false;
break;
}
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId())) {
useSkillSelf = false;
}
if (useSkillSelf) {
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.getKnownList().knowsObject(attackTarget))) {
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
} else {
double dx = _actor.getX() - attackTarget.getX();
double dy = _actor.getY() - attackTarget.getY();
double dz = _actor.getZ() - attackTarget.getZ();
double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) // 1800 * 1800
&& (_actor.getKnownList().knowsObject(attackTarget))) {
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
} else
// Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage) {
range = _selfAnalysis.maxCastRange - 50;
}
if (_actor.getWalkSpeed() <= 0) {
return;
}
if (attackTarget.isMoving()) {
moveToPawn(attackTarget, range - 70);
} else {
moveToPawn(attackTarget, range);
}
}
}
}
}
// Else, if the actor is muted and far from target, just "move to pawn"
else if (_actor.isMuted() && (dist_2 > (range * range))) {
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
double dz = _actor.getZ() - attackTarget.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage) {
range = _selfAnalysis.maxCastRange - 50;
}
if (_actor.getWalkSpeed() <= 0) {
return;
}
if (attackTarget.isMoving()) {
moveToPawn(attackTarget, range - 70);
} else {
moveToPawn(attackTarget, range);
}
}
}
// Else, if this is close enough to attack
else if (dist_2 <= (range * range)) {
// Force mobs to attack anybody if confused
L2Character hated;
if (_actor.isConfused()) {
hated = attackTarget;
} else {
hated = ((L2Attackable) _actor).getMostHated();
}
if (hated == null) {
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget) {
attackTarget = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5)) {
for (Skill sk : skills) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume2(sk)) && !_actor.isSkillDisabled(sk)) {
L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HP))) {
boolean useSkillSelf = true;
if ((sk.hasEffectType(L2EffectType.HP)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) {
useSkillSelf = false;
break;
}
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId())) {
useSkillSelf = false;
}
if (useSkillSelf) {
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
// Finally, do the physical attack itself
_actor.doAttack(attackTarget);
}
}
/**
* Manage AI thinking actions of a L2Attackable.
*/
@Override
protected void onEvtThink() {
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
// setIntention(AI_INTENTION_IDLE);
// Check if the actor can't use skills and if a thinking action isn't already in progress
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled()) {
return;
}
// Start thinking action
_thinking = true;
try {
// Manage AI thinks of a L2Attackable
if (getIntention() == AI_INTENTION_ACTIVE) {
thinkActive();
} else if (getIntention() == AI_INTENTION_ATTACK) {
thinkAttack();
}
} finally {
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* </ul>
* @param attacker The L2Character that attacks the actor
*/
@Override
protected void onEvtAttacked(L2Character attacker) {
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0) {
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK) {
setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li>
* </ul>
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(L2Character target, long aggro) {
if (_actor == null) {
return;
}
L2Attackable me = (L2Attackable) _actor;
if (target != null) {
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
aggro = me.getHating(target);
if (aggro <= 0) {
if (me.getMostHated() == null) {
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK) {
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
L2DefenderInstance sGuard;
if (_actor instanceof L2FortCommanderInstance) {
sGuard = (L2FortCommanderInstance) _actor;
} else {
sGuard = (L2DefenderInstance) _actor;
}
double homeX = target.getX() - sGuard.getSpawn().getX();
double homeY = target.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000) {
setIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
}
}
} else {
// currently only for setting lower general aggro
if (aggro >= 0) {
return;
}
L2Character mostHated = me.getMostHated();
if (mostHated == null) {
_globalAggro = -25;
return;
}
for (var attacker : me.getAggroList().keySet()) {
me.addDamageHate(attacker, 0, aggro);
}
aggro = me.getHating(mostHated);
if (aggro <= 0) {
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
public void stopAITask() {
if (_aiTask != null) {
_aiTask.cancel(false);
_aiTask = null;
}
_actor.detachAI();
super.stopAITask();
}
}
@@ -0,0 +1,104 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.model.zone.ZoneId;
import com.l2jserver.gameserver.network.SystemMessageId;
/**
* Playable creatures AI.
* @author JIV
*/
public abstract class L2PlayableAI extends L2CharacterAI {
public L2PlayableAI(L2Playable creature) {
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target) {
if (target instanceof L2Playable) {
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP))) {
// If attacker have karma and have level >= 10 than his target and target have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
return;
}
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getKarma() > 0) && !(target.isInsideZone(ZoneId.PVP))) {
// If target have karma and have level >= 10 than his target and actor have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
return;
}
if (target.getActingPlayer().isCursedWeaponEquipped() && (_actor.getActingPlayer().getLevel() <= 20)) {
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
return;
}
if (_actor.getActingPlayer().isCursedWeaponEquipped() && (target.getActingPlayer().getLevel() <= 20)) {
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
return;
}
}
super.onIntentionAttack(target);
}
@Override
protected void onIntentionCast(Skill skill, L2Object target) {
if ((target instanceof L2Playable) && skill.isBad()) {
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getKarma() > 0) && !target.isInsideZone(ZoneId.PVP)) {
// If attacker have karma and have level >= 10 than his target and target have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getKarma() > 0) && !target.isInsideZone(ZoneId.PVP)) {
// If target have karma and have level >= 10 than his target and actor have
// Newbie Protection Buff,
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
if (target.getActingPlayer().isCursedWeaponEquipped() && ((_actor.getActingPlayer().getLevel() <= 20) || (target.getActingPlayer().getLevel() <= 20))) {
_actor.getActingPlayer().sendPacket(SystemMessageId.TARGET_IS_INCORRECT);
clientActionFailed();
_actor.setIsCastingNow(false);
return;
}
}
super.onIntentionCast(skill, target);
}
}
@@ -0,0 +1,305 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_CAST;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_MOVE_TO;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import com.l2jserver.gameserver.enums.DuelState;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.model.skills.targets.TargetType;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
public class L2PlayerAI extends L2PlayableAI {
private boolean _thinking; // to prevent recursive thinking
private IntentionCommand _nextIntention = null;
public L2PlayerAI(L2PcInstance creature) {
super(creature);
}
void saveNextIntention(CtrlIntention intention, Object arg0, Object arg1) {
_nextIntention = new IntentionCommand(intention, arg0, arg1);
}
@Override
public IntentionCommand getNextIntention() {
return _nextIntention;
}
/**
* Saves the current Intention for this L2PlayerAI if necessary and calls changeIntention in AbstractAI.
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
protected synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1) {
// Forget next if it's not cast or it's cast and skill is toggle.
if ((intention != AI_INTENTION_CAST) || ((arg0 != null) && !((Skill) arg0).isToggle())) {
_nextIntention = null;
super.changeIntention(intention, arg0, arg1);
return;
}
// do nothing if next intention is same as current one.
if ((intention == _intention) && (arg0 == _intentionArg0) && (arg1 == _intentionArg1)) {
super.changeIntention(intention, arg0, arg1);
return;
}
// save current intention so it can be used after cast
saveNextIntention(_intention, _intentionArg0, _intentionArg1);
super.changeIntention(intention, arg0, arg1);
}
/**
* Launch actions corresponding to the Event ReadyToAct.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Launch actions corresponding to the Event Think</li>
* </ul>
*/
@Override
protected void onEvtReadyToAct() {
// Launch actions corresponding to the Event Think
if (_nextIntention != null) {
setIntention(_nextIntention._crtlIntention, _nextIntention._arg0, _nextIntention._arg1);
_nextIntention = null;
}
super.onEvtReadyToAct();
}
/**
* Launch actions corresponding to the Event Cancel.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Stop an AI Follow Task</li>
* <li>Launch actions corresponding to the Event Think</li>
* </ul>
*/
@Override
protected void onEvtCancel() {
_nextIntention = null;
super.onEvtCancel();
}
/**
* Finalize the casting of a skill. This method overrides L2CharacterAI method.<br>
* <B>What it does:</B><br>
* Check if actual intention is set to CAST and, if so, retrieves latest intention before the actual CAST and set it as the current intention for the player.
*/
@Override
protected void onEvtFinishCasting() {
if (getIntention() == AI_INTENTION_CAST) {
// run interrupted or next intention
IntentionCommand nextIntention = _nextIntention;
if (nextIntention != null) {
if (nextIntention._crtlIntention != AI_INTENTION_CAST) // previous state shouldn't be casting
{
setIntention(nextIntention._crtlIntention, nextIntention._arg0, nextIntention._arg1);
} else {
setIntention(AI_INTENTION_IDLE);
}
} else {
// set intention to idle if skill doesn't change intention.
setIntention(AI_INTENTION_IDLE);
}
}
}
@Override
protected void onIntentionRest() {
if (getIntention() != AI_INTENTION_REST) {
changeIntention(AI_INTENTION_REST, null, null);
setTarget(null);
if (getAttackTarget() != null) {
setAttackTarget(null);
}
clientStopMoving(null);
}
}
@Override
protected void onIntentionActive() {
setIntention(AI_INTENTION_IDLE);
}
/**
* Manage the Move To Intention : Stop current Attack and Launch a Move to Location Task.<br>
* <B><U> Actions</U> : </B>
* <ul>
* <li>Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)</li>
* <li>Set the Intention of this AI to AI_INTENTION_MOVE_TO</li>
* <li>Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)</li>
* </ul>
*/
@Override
protected void onIntentionMoveTo(Location loc) {
if (getIntention() == AI_INTENTION_REST) {
// Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor
clientActionFailed();
return;
}
if (_actor.getActingPlayer().getDuelState() == DuelState.DEAD) {
clientActionFailed();
_actor.getActingPlayer().sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANNOT_MOVE_FROZEN));
return;
}
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isAttackingNow()) {
clientActionFailed();
saveNextIntention(AI_INTENTION_MOVE_TO, loc, null);
return;
}
// Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
clientStopAutoAttack();
// Set the Intention of this AbstractAI to AI_INTENTION_MOVE_TO
changeIntention(AI_INTENTION_MOVE_TO, loc, null);
// Abort the attack of the L2Character and send Server->Client ActionFailed packet
_actor.abortAttack();
// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
moveTo(loc.getX(), loc.getY(), loc.getZ());
}
@Override
protected void clientNotifyDead() {
_clientMovingToPawnOffset = 0;
_clientMoving = false;
super.clientNotifyDead();
}
private void thinkAttack() {
L2Character target = getAttackTarget();
if (target == null) {
return;
}
if (checkTargetLostOrDead(target)) {
// Notify the target
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange())) {
return;
}
clientStopMoving(null);
_actor.doAttack(target);
}
private void thinkCast() {
L2Character target = getCastTarget();
if ((_skill.getTargetType() == TargetType.GROUND) && (_actor instanceof L2PcInstance)) {
if (maybeMoveToPosition(((L2PcInstance) _actor).getCurrentSkillWorldPosition(), _actor.getMagicalAttackRange(_skill))) {
_actor.setIsCastingNow(false);
return;
}
} else {
if (checkTargetLost(target)) {
if (_skill.isBad() && (getAttackTarget() != null)) {
// Notify the target
setCastTarget(null);
}
_actor.setIsCastingNow(false);
return;
}
if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill))) {
_actor.setIsCastingNow(false);
return;
}
}
if ((_skill.getHitTime() > 50) && !_skill.isSimultaneousCast()) {
clientStopMoving(null);
}
_actor.doCast(_skill);
}
private void thinkPickUp() {
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow()) {
return;
}
L2Object target = getTarget();
if (checkTargetLost(target)) {
return;
}
if (maybeMoveToPawn(target, 36)) {
return;
}
setIntention(AI_INTENTION_IDLE);
_actor.getActingPlayer().doPickupItem(target);
}
private void thinkInteract() {
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow()) {
return;
}
L2Object target = getTarget();
if (checkTargetLost(target)) {
return;
}
if (maybeMoveToPawn(target, 36)) {
return;
}
if (!(target instanceof L2StaticObjectInstance)) {
_actor.getActingPlayer().doInteract((L2Character) target);
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink() {
if (_thinking && (getIntention() != AI_INTENTION_CAST)) {
return;
}
_thinking = true;
try {
if (getIntention() == AI_INTENTION_ATTACK) {
thinkAttack();
} else if (getIntention() == AI_INTENTION_CAST) {
thinkCast();
} else if (getIntention() == AI_INTENTION_PICK_UP) {
thinkPickUp();
} else if (getIntention() == AI_INTENTION_INTERACT) {
thinkInteract();
}
} finally {
_thinking = false;
}
}
}
@@ -0,0 +1,784 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.Collection;
import java.util.concurrent.Future;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.GameTimeController;
import com.l2jserver.gameserver.GeoData;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Attackable;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.actor.instance.L2DefenderInstance;
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.effects.L2EffectType;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.util.Util;
/**
* Siege guard AI.
*/
public class L2SiegeGuardAI extends L2CharacterAI implements Runnable {
private static final int MAX_ATTACK_TIMEOUT = 30 * GameTimeController.TICKS_PER_SECOND; // 30s converted to ticks
/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> _aiTask;
/** For attack AI, analysis of mob and its targets */
private final SelfAnalysis _selfAnalysis = new SelfAnalysis();
// private TargetAnalysis _mostHatedAnalysis = new TargetAnalysis();
/** The delay after which the attacked is stopped */
private int _attackTimeout;
/** The L2Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean _thinking; // to prevent recursive thinking
private final int _attackRange;
/**
* Constructor of L2AttackableAI.
* @param creature the creature
*/
public L2SiegeGuardAI(L2DefenderInstance creature) {
super(creature);
_selfAnalysis.init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
_attackRange = _actor.getPhysicalAttackRange();
}
@Override
public void run() {
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* <B><U> Actor is a L2GuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* <li>The L2MonsterInstance target is aggressive</li>
* </ul>
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The L2PcInstance target isn't a Defender</li>
* </ul>
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* </ul>
* <B><U> Actor is a L2MonsterInstance</U> :</B>
* <ul>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li>
* </ul>
* @param target The targeted L2Object
* @return True if the target is autoattackable (depends on the actor type).
*/
protected boolean autoAttackCondition(L2Character target) {
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof L2DefenderInstance) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance) || target.isAlikeDead()) {
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul()) {
// However EffectInvincible requires to check GMs specially
if (target.isPlayer() && target.isGM()) {
return false;
}
if (target.isSummon() && ((L2Summon) target).getOwner().isGM()) {
return false;
}
}
// Get the owner if the target is a summon
if (target instanceof L2Summon summon) {
L2PcInstance owner = summon.getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false)) {
target = owner;
}
}
// Check if the target is a L2PcInstance
if (target instanceof L2Playable playable) {
// Check if the target isn't in silent move mode AND too far (>100)
if (playable.isSilentMovingAffected() && !_actor.isInsideRadius(target, 250, false, false)) {
return false;
}
}
// Los Check Here
return (_actor.isAutoAttackable(target) && GeoData.getInstance().canSeeTarget(_actor, target));
}
/**
* Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<br>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1) {
LOG.debug("{}: changeIntention({}, {}, {})", getClass().getSimpleName(), intention, arg0, arg1);
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead()) {
L2Attackable npc = (L2Attackable) _actor;
// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
if (!npc.getKnownList().getKnownPlayers().isEmpty()) {
intention = AI_INTENTION_ACTIVE;
} else {
intention = AI_INTENTION_IDLE;
}
}
if (intention == AI_INTENTION_IDLE) {
// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (_aiTask != null) {
_aiTask.cancel(true);
_aiTask = null;
}
// Cancel the AI
_actor.detachAI();
return;
}
}
// Set the Intention of this L2AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if ((_aiTask == null) && (_actor.isInActiveRegion())) {
_aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.
* @param target The L2Character to attack
*/
@Override
protected void onIntentionAttack(L2Character target) {
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
* </ul>
*/
private void thinkActive() {
L2Attackable npc = (L2Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0) {
if (_globalAggro < 0) {
_globalAggro++;
} else {
_globalAggro--;
}
}
// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0) {
for (L2Character target : npc.getKnownList().getKnownCharactersInRadius(_attackRange)) {
if (target == null) {
continue;
}
if (autoAttackCondition(target)) // check aggression
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
long hating = npc.getHating(target);
// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
if (hating == 0) {
npc.addDamageHate(target, 0, 1);
}
}
}
// Chose a target from its aggroList
L2Character hated;
if (_actor.isConfused()) {
hated = getAttackTarget(); // Force mobs to attack anybody if confused
} else {
hated = npc.getMostHated();
// _mostHatedAnalysis.Update(hated);
}
// Order to the L2Attackable to attack the target
if (hated != null) {
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
long aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0) {
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the L2DefenderInstance to return to its home location because there's no target to attack
((L2DefenderInstance) _actor).returnHome();
}
/**
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all L2Object of its Faction inside the Faction Range</li>
* <li>Chose a target and order to attack it with magic skill or physical attack</li>
* </ul>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack() {
LOG.debug("{}: thinkAttack(); timeout={}", getClass().getSimpleName(), (_attackTimeout - GameTimeController.getInstance().getGameTicks()));
if (_attackTimeout < GameTimeController.getInstance().getGameTicks()) {
// Check if the actor is running
if (_actor.isRunning()) {
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
}
}
L2Character attackTarget = getAttackTarget();
// Check if target is dead or if timeout is expired to stop this attack
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks())) {
// Stop hating this target after the attack timeout or if target is dead
if (attackTarget != null) {
L2Attackable npc = (L2Attackable) _actor;
npc.stopHating(attackTarget);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
setAttackTarget(null);
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private void factionNotifyAndSupport() {
L2Character target = getAttackTarget();
final var siegeGuard = (L2Npc) _actor;
// Call all L2Object of its Faction inside the Faction Range
if ((siegeGuard.getTemplate().getClans() == null) || (target == null)) {
return;
}
if (target.isInvul()) {
return; // speeding it up for siege guards
}
// Go through all L2Character that belong to its faction
for (var cha : _actor.getKnownList().getKnownCharactersInRadius(1000)) {
if (cha == null) {
continue;
}
if (!(cha instanceof L2Npc npc)) {
if (_selfAnalysis.hasHealOrResurrect && (cha instanceof L2PcInstance) && (siegeGuard.getCastle().getSiege().checkIsDefender(cha.getClan()))) {
// heal friends
if (!_actor.isAttackingDisabled() && (cha.getCurrentHp() < (cha.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2.0)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2.0)) && cha.isInCombat()) {
for (Skill sk : _selfAnalysis.healSkills) {
if (_actor.getCurrentMp() < sk.getMpConsume2()) {
continue;
}
if (_actor.isSkillDisabled(sk)) {
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, cha, true)) {
continue;
}
int chance = 5;
if (chance >= Rnd.get(100)) {
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, cha)) {
break;
}
L2Object OldTarget = _actor.getTarget();
_actor.setTarget(cha);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
continue;
}
if (!npc.isInMyClan(siegeGuard)) {
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600)
// && _actor.getAttackByList().contains(getAttackTarget())
&& ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE))
// limiting aggro for siege guards
&& target.isInsideRadius(npc, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target)) {
// Notify the L2Object AI with EVT_AGGRESSION
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
return;
}
// heal friends
if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2.0)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2.0)) && npc.isInCombat()) {
for (Skill sk : _selfAnalysis.healSkills) {
if (_actor.getCurrentMp() < sk.getMpConsume2()) {
continue;
}
if (_actor.isSkillDisabled(sk)) {
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true)) {
continue;
}
int chance = 4;
if (chance >= Rnd.get(100)) {
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, npc)) {
break;
}
L2Object OldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
}
}
private void attackPrepare() {
// Get all information needed to choose between physical or magical attack
Collection<Skill> skills;
double dist_2;
int range;
L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
L2Character attackTarget = getAttackTarget();
try {
_actor.setTarget(attackTarget);
skills = _actor.getAllSkills();
dist_2 = _actor.calculateDistance(attackTarget, false, true);
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
if (attackTarget.isMoving()) {
range += 50;
}
} catch (NullPointerException e) {
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if (attackTarget instanceof L2PcInstance) {
if ((sGuard.getConquerableHall() == null) && sGuard.getCastle().getSiege().checkIsDefender(attackTarget.getClan())) {
// Cancel the target
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
}
if (!GeoData.getInstance().canSeeTarget(_actor, attackTarget)) {
// Siege guards differ from normal mobs currently:
// If target cannot seen, don't attack any more
sGuard.stopHating(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist_2 > (range * range))) {
// check for long ranged skills and heal/buff skills
for (Skill sk : skills) {
int castRange = sk.getCastRange();
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume2(sk)) && !sk.isPassive()) {
L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HP))) {
boolean useSkillSelf = true;
if ((sk.hasEffectType(L2EffectType.HP)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) {
useSkillSelf = false;
break;
}
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId())) {
useSkillSelf = false;
}
if (useSkillSelf) {
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.getKnownList().knowsObject(attackTarget))) {
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
} else {
double dx = _actor.getX() - attackTarget.getX();
double dy = _actor.getY() - attackTarget.getY();
double dz = _actor.getZ() - attackTarget.getZ();
double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) // 1800 * 1800
&& (_actor.getKnownList().knowsObject(attackTarget))) {
// Cancel the target
_actor.getKnownList().removeKnownObject(attackTarget);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
} else
// Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isHealer) {
return;
}
if (_selfAnalysis.isMage) {
range = _selfAnalysis.maxCastRange - 50;
}
if (attackTarget.isMoving()) {
moveToPawn(attackTarget, range - 70);
} else {
moveToPawn(attackTarget, range);
}
}
}
}
}
// Else, if the actor is muted and far from target, just "move to pawn"
else if (_actor.isMuted() && (dist_2 > (range * range)) && !_selfAnalysis.isHealer) {
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
double dz = _actor.getZ() - attackTarget.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage) {
range = _selfAnalysis.maxCastRange - 50;
}
if (attackTarget.isMoving()) {
moveToPawn(attackTarget, range - 70);
} else {
moveToPawn(attackTarget, range);
}
}
}
// Else, if this is close enough to attack
else if (dist_2 <= (range * range)) {
// Force mobs to attack anybody if confused
L2Character hated;
if (_actor.isConfused()) {
hated = attackTarget;
} else {
hated = ((L2Attackable) _actor).getMostHated();
}
if (hated == null) {
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != attackTarget) {
attackTarget = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5)) {
for (Skill sk : skills) {
int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume2(sk)) && !_actor.isSkillDisabled(sk)) {
L2Object OldTarget = _actor.getTarget();
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HP))) {
boolean useSkillSelf = true;
if ((sk.hasEffectType(L2EffectType.HP)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5))) {
useSkillSelf = false;
break;
}
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId())) {
useSkillSelf = false;
}
if (useSkillSelf) {
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_actor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
// Finally, do the physical attack itself
if (!_selfAnalysis.isHealer) {
_actor.doAttack(attackTarget);
}
}
}
/**
* Manage AI thinking actions of a L2Attackable.
*/
@Override
protected void onEvtThink() {
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
// setIntention(AI_INTENTION_IDLE);
// Check if the thinking action is already in progress
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled()) {
return;
}
// Start thinking action
_thinking = true;
try {
// Manage AI thinks of a L2Attackable
if (getIntention() == AI_INTENTION_ACTIVE) {
thinkActive();
} else if (getIntention() == AI_INTENTION_ATTACK) {
thinkAttack();
}
} finally {
// Stop thinking action
_thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li>
* </ul>
* @param attacker The L2Character that attacks the actor
*/
@Override
protected void onEvtAttacked(L2Character attacker) {
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0) {
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK) {
setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<br>
* <B><U> Actions</U> :</B>
* <ul>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li>
* </ul>
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(L2Character target, long aggro) {
if (_actor == null) {
return;
}
L2Attackable me = (L2Attackable) _actor;
if (target != null) {
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
aggro = me.getHating(target);
if (aggro <= 0) {
if (me.getMostHated() == null) {
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK) {
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning()) {
_actor.setRunning();
}
L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
double homeX = target.getX() - sGuard.getSpawn().getX();
double homeY = target.getY() - sGuard.getSpawn().getY();
// Check if the L2SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000) {
setIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
}
}
} else {
// currently only for setting lower general aggro
if (aggro >= 0) {
return;
}
L2Character mostHated = me.getMostHated();
if (mostHated == null) {
_globalAggro = -25;
return;
}
for (L2Character aggroed : me.getAggroList().keySet()) {
me.addDamageHate(aggroed, 0, aggro);
}
aggro = me.getHating(mostHated);
if (aggro <= 0) {
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
public void stopAITask() {
if (_aiTask != null) {
_aiTask.cancel(false);
_aiTask = null;
}
_actor.detachAI();
super.stopAITask();
}
}
@@ -0,0 +1,49 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import java.util.ArrayList;
import java.util.List;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.instance.L2DefenderInstance;
/**
* @author BiggBoss
*/
public final class L2SpecialSiegeGuardAI extends L2SiegeGuardAI {
private final List<Integer> _allied = new ArrayList<>();
public L2SpecialSiegeGuardAI(L2DefenderInstance creature) {
super(creature);
}
public List<Integer> getAlly() {
return _allied;
}
@Override
protected boolean autoAttackCondition(L2Character target) {
if (_allied.contains(target.getObjectId())) {
return false;
}
return super.autoAttackCondition(target);
}
}
@@ -0,0 +1,243 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static com.l2jserver.gameserver.config.Configuration.geodata;
import java.util.concurrent.Future;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.GeoData;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.pathfinding.PathFinding;
public class L2SummonAI extends L2PlayableAI implements Runnable {
private static final int AVOID_RADIUS = 70;
private volatile boolean _thinking; // to prevent recursive thinking
private volatile boolean _startFollow = ((L2Summon) _actor).getFollowStatus();
private L2Character _lastAttack = null;
private volatile boolean _startAvoid = false;
private Future<?> _avoidTask = null;
public L2SummonAI(L2Summon creature) {
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target) {
if ((geodata().getPathFinding() > 0) && //
(PathFinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId(), true) == null)) {
return;
}
super.onIntentionAttack(target);
}
@Override
protected void onIntentionIdle() {
stopFollow();
_startFollow = false;
onIntentionActive();
}
@Override
protected void onIntentionActive() {
L2Summon summon = (L2Summon) _actor;
if (_startFollow) {
setIntention(AI_INTENTION_FOLLOW, summon.getOwner());
} else {
super.onIntentionActive();
}
}
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1) {
switch (intention) {
case AI_INTENTION_ACTIVE, AI_INTENTION_FOLLOW -> startAvoidTask();
default -> stopAvoidTask();
}
super.changeIntention(intention, arg0, arg1);
}
private void thinkAttack() {
if (checkTargetLostOrDead(getAttackTarget())) {
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(getAttackTarget(), _actor.getPhysicalAttackRange())) {
return;
}
clientStopMoving(null);
_actor.doAttack(getAttackTarget());
}
private void thinkCast() {
L2Summon summon = (L2Summon) _actor;
if (checkTargetLost(getCastTarget())) {
setCastTarget(null);
return;
}
boolean val = _startFollow;
if (maybeMoveToPawn(getCastTarget(), _actor.getMagicalAttackRange(_skill))) {
return;
}
clientStopMoving(null);
summon.setFollowStatus(false);
setIntention(AI_INTENTION_IDLE);
_startFollow = val;
_actor.doCast(_skill);
}
private void thinkPickUp() {
if (checkTargetLost(getTarget())) {
return;
}
if (maybeMoveToPawn(getTarget(), 36)) {
return;
}
setIntention(AI_INTENTION_IDLE);
((L2Summon) _actor).doPickupItem(getTarget());
}
private void thinkInteract() {
if (checkTargetLost(getTarget())) {
return;
}
if (maybeMoveToPawn(getTarget(), 36)) {
return;
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink() {
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled()) {
return;
}
_thinking = true;
try {
switch (getIntention()) {
case AI_INTENTION_ATTACK -> thinkAttack();
case AI_INTENTION_CAST -> thinkCast();
case AI_INTENTION_PICK_UP -> thinkPickUp();
case AI_INTENTION_INTERACT -> thinkInteract();
}
} finally {
_thinking = false;
}
}
@Override
protected void onEvtFinishCasting() {
if (_lastAttack == null) {
((L2Summon) _actor).setFollowStatus(_startFollow);
} else {
setIntention(CtrlIntention.AI_INTENTION_ATTACK, _lastAttack);
_lastAttack = null;
}
}
@Override
protected void onEvtAttacked(L2Character attacker) {
super.onEvtAttacked(attacker);
avoidAttack(attacker);
}
@Override
protected void onEvtEvaded(L2Character attacker) {
super.onEvtEvaded(attacker);
avoidAttack(attacker);
}
private void avoidAttack(L2Character attacker) {
// trying to avoid if summon near owner
if ((((L2Summon) _actor).getOwner() != null) && (((L2Summon) _actor).getOwner() != attacker) && ((L2Summon) _actor).getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false)) {
_startAvoid = true;
}
}
@Override
public void run() {
if (_startAvoid) {
_startAvoid = false;
if (!_clientMoving && !_actor.isDead() && !_actor.isMovementDisabled()) {
final int ownerX = ((L2Summon) _actor).getOwner().getX();
final int ownerY = ((L2Summon) _actor).getOwner().getY();
final double angle = Math.toRadians(Rnd.get(-90, 90)) + Math.atan2(ownerY - _actor.getY(), ownerX - _actor.getX());
final int targetX = ownerX + (int) (AVOID_RADIUS * Math.cos(angle));
final int targetY = ownerY + (int) (AVOID_RADIUS * Math.sin(angle));
if (GeoData.getInstance().canMove(_actor.getX(), _actor.getY(), _actor.getZ(), targetX, targetY, _actor.getZ(), _actor.getInstanceId())) {
moveTo(targetX, targetY, _actor.getZ());
}
}
}
}
public void notifyFollowStatusChange() {
_startFollow = !_startFollow;
switch (getIntention()) {
case AI_INTENTION_ACTIVE, AI_INTENTION_FOLLOW, AI_INTENTION_IDLE, AI_INTENTION_MOVE_TO, AI_INTENTION_PICK_UP -> ((L2Summon) _actor).setFollowStatus(_startFollow);
}
}
public void setStartFollowController(boolean val) {
_startFollow = val;
}
@Override
protected void onIntentionCast(Skill skill, L2Object target) {
if (getIntention() == AI_INTENTION_ATTACK) {
_lastAttack = getAttackTarget();
} else {
_lastAttack = null;
}
super.onIntentionCast(skill, target);
}
private void startAvoidTask() {
if (_avoidTask == null) {
_avoidTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 100, 100);
}
}
private void stopAvoidTask() {
if (_avoidTask != null) {
_avoidTask.cancel(false);
_avoidTask = null;
}
}
@Override
public void stopAITask() {
stopAvoidTask();
super.stopAITask();
}
}
@@ -0,0 +1,106 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Vehicle;
import com.l2jserver.gameserver.model.skills.Skill;
/**
* @author DS
*/
public abstract class L2VehicleAI extends L2CharacterAI {
public L2VehicleAI(L2Vehicle creature) {
super(creature);
}
@Override
protected void onIntentionAttack(L2Character target) {
}
@Override
protected void onIntentionCast(Skill skill, L2Object target) {
}
@Override
protected void onIntentionFollow(L2Character target) {
}
@Override
protected void onIntentionPickUp(L2Object item) {
}
@Override
protected void onIntentionInteract(L2Object object) {
}
@Override
protected void onEvtAttacked(L2Character attacker) {
}
@Override
protected void onEvtAggression(L2Character target, long aggro) {
}
@Override
protected void onEvtStunned(L2Character attacker) {
}
@Override
protected void onEvtSleeping(L2Character attacker) {
}
@Override
protected void onEvtRooted(L2Character attacker) {
}
@Override
protected void onEvtForgetObject(L2Object object) {
}
@Override
protected void onEvtCancel() {
}
@Override
protected void onEvtDead() {
}
@Override
protected void onEvtFakeDeath() {
}
@Override
protected void onEvtFinishCasting() {
}
@Override
protected void clientActionFailed() {
}
@Override
protected void moveToPawn(L2Object pawn, int offset) {
}
@Override
protected void clientStoppedMoving() {
}
}
@@ -0,0 +1,55 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.ai;
import java.util.List;
/**
* Class for AI action after some event.<br>
* Has 2 lists for "work" and "break".
* @author Yaroslav
* @author Zoey76
*/
public class NextAction {
public interface NextActionCallback {
void doWork();
}
private final List<CtrlEvent> _events;
private final List<CtrlIntention> _intentions;
private final NextActionCallback _callback;
public NextAction(CtrlEvent event, CtrlIntention intention, NextActionCallback callback) {
_events = List.of(event);
_intentions = List.of(intention);
_callback = callback;
}
public void doAction() {
_callback.doWork();
}
public List<CtrlEvent> getEvents() {
return _events;
}
public List<CtrlIntention> getIntentions() {
return _intentions;
}
}
@@ -0,0 +1,134 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Forum.
* @author Zoey76
* @version 2.6.2.0
*/
public final class Forum {
private int id;
private String name;
private ForumType type;
private int post;
private ForumVisibility visibility;
private final Forum parent;
private int ownerId;
private final Map<String, Forum> children = new ConcurrentHashMap<>();
private final Map<Integer, Topic> topics = new ConcurrentHashMap<>();
public Forum(int id, String name, Forum parent, ForumType type, ForumVisibility visibility, int ownerId) {
this.name = name;
this.id = id;
this.type = type;
post = 0;
this.visibility = visibility;
this.parent = parent;
this.ownerId = ownerId;
}
public int getTopicSize() {
return topics.size();
}
public Topic getTopic(int topicId) {
return topics.get(topicId);
}
public void addTopic(Topic t) {
topics.put(t.getId(), t);
}
public void removeTopic(int topicId) {
topics.remove(topicId);
}
public Forum getChildByName(String name) {
return children.get(name);
}
public void addChild(Forum child) {
children.put(child.getName(), child);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ForumType getType() {
return type;
}
public void setType(ForumType type) {
this.type = type;
}
public int getPost() {
return post;
}
public void setPost(int post) {
this.post = post;
}
public ForumVisibility getVisibility() {
return visibility;
}
public void setVisibility(ForumVisibility visibility) {
this.visibility = visibility;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public Forum getParent() {
return parent;
}
public Map<String, Forum> getChildren() {
return children;
}
public Map<Integer, Topic> getTopics() {
return topics;
}
}
@@ -0,0 +1,32 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
/**
* Forum Type.
* @author Zoey76
* @version 2.6.2.0
*/
public enum ForumType {
ROOT,
NORMAL,
CLAN,
MEMO,
MAIL
}
@@ -0,0 +1,31 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
/**
* Forum Visibility.
* @author Zoey76
* @version 2.6.2.0
*/
public enum ForumVisibility {
INVISIBLE,
ALL,
CLAN_MEMBER_ONLY,
OWNER_ONLY
}
@@ -0,0 +1,107 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
/**
* Post.
* @author Zoey76
* @version 2.6.2.0
*/
public class Post {
private int id;
private String ownerName;
private int ownerId;
private long date;
private int topicId;
private int forumId;
private String txt;
public Post(int id, String ownerName, int ownerId, long date, int topicId, int forumId, String txt) {
this.id = id;
this.ownerName = ownerName;
this.ownerId = ownerId;
this.date = date;
this.topicId = topicId;
this.forumId = forumId;
this.txt = txt;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public int getTopicId() {
return topicId;
}
public void setTopicId(int topicId) {
this.topicId = topicId;
}
public int getForumId() {
return forumId;
}
public void setForumId(int forumId) {
this.forumId = forumId;
}
public String getTxt() {
return txt;
}
public void setTxt(String txt) {
this.txt = txt;
}
}
@@ -0,0 +1,86 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
/**
* Topic.
* @author Zoey76
* @version 2.6.2.0
*/
public class Topic {
private final int id;
private final int forumId;
private final String name;
private final long date;
private final String ownerName;
private final int ownerId;
private final TopicType type;
private final int reply;
public Topic(int id, int forumId, String name, long date, String ownerName, int ownerId, TopicType type, int reply) {
this.id = id;
this.forumId = forumId;
this.name = name;
this.date = date;
this.ownerName = ownerName;
this.ownerId = ownerId;
this.type = type;
this.reply = reply;
}
public int getId() {
return id;
}
public int getForumId() {
return forumId;
}
public String getName() {
return name;
}
public long getDate() {
return date;
}
public String getOwnerName() {
return ownerName;
}
public int getOwnerId() {
return ownerId;
}
public TopicType getType() {
return type;
}
public int getReply() {
return reply;
}
}
@@ -0,0 +1,29 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.model;
/**
* TopicType.
* @author Zoey76
* @version 2.6.2.0
*/
public enum TopicType {
NORMAL,
MEMO
}
@@ -0,0 +1,35 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository;
import java.util.Map;
import com.l2jserver.gameserver.bbs.model.Forum;
/**
* Forum repository.
* @author Zoey76
* @version 2.6.2.0
*/
public interface ForumRepository {
Map<String, Forum> getForums();
void save(Forum forum);
}
@@ -0,0 +1,38 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository;
import java.util.List;
import com.l2jserver.gameserver.bbs.model.Post;
import com.l2jserver.gameserver.bbs.model.Topic;
/**
* Post repository.
* @author Zoey76
* @version 2.6.2.0
*/
public interface PostRepository {
void delete(Topic topic);
void update(Post post);
List<Post> load(Topic topic);
}
@@ -0,0 +1,36 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.Topic;
/**
* Topic repository.
* @author Zoey76
* @version 2.6.2.0
*/
public interface TopicRepository {
void load(Forum forum);
void save(Topic topic);
void delete(Topic topic, Forum forum);
}
@@ -0,0 +1,119 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository.impl;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.ForumType;
import com.l2jserver.gameserver.bbs.model.ForumVisibility;
import com.l2jserver.gameserver.bbs.repository.ForumRepository;
import com.l2jserver.gameserver.bbs.service.ForumsBBSManager;
import com.l2jserver.gameserver.dao.factory.impl.DAOFactory;
/**
* Forum repository MySQL implementation.
* @author Zoey76
* @version 2.6.2.0
*/
public class ForumRepositoryMySQLImpl implements ForumRepository {
private static final Logger LOG = LoggerFactory.getLogger(ForumRepositoryMySQLImpl.class);
private static final String SELECT_FORUMS = "SELECT forum_id, forum_name, forum_post, forum_type, forum_perm, forum_owner_id FROM forums WHERE forum_type = 0";
private static final String SELECT_FORUM_CHILDREN = "SELECT forum_id, forum_name, forum_post, forum_type, forum_perm, forum_owner_id FROM forums WHERE forum_parent=?";
private static final String INSERT_FORUM = "INSERT INTO forums (forum_name, forum_parent, forum_post, forum_type, forum_perm, forum_owner_id) VALUES (?,?,?,?,?,?)";
@Override
public Map<String, Forum> getForums() {
final var forums = new HashMap<String, Forum>();
try (var con = ConnectionFactory.getInstance().getConnection();
var s = con.createStatement();
var rs = s.executeQuery(SELECT_FORUMS)) {
while (rs.next()) {
final var forum = new Forum(rs.getInt("forum_id"), //
rs.getString("forum_name"), //
null, //
ForumType.values()[rs.getInt("forum_type")], //
ForumVisibility.values()[rs.getInt("forum_perm")], //
rs.getInt("forum_owner_id"));
forums.put(forum.getName(), forum);
// Load topic
DAOFactory.getInstance().getTopicRepository().load(forum);
// Load children
loadChildren(forum);
}
} catch (Exception ex) {
LOG.warn("Data error on Forum (root)!", ex);
}
return forums;
}
private void loadChildren(Forum parent) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(SELECT_FORUM_CHILDREN)) {
ps.setInt(1, parent.getId());
try (var rs = ps.executeQuery()) {
while (rs.next()) {
ForumsBBSManager.getInstance().load(rs.getInt("forum_id"), //
rs.getString("forum_name"), //
parent, //
ForumType.values()[rs.getInt("forum_type")], //
ForumVisibility.values()[rs.getInt("forum_perm")], //
rs.getInt("forum_owner_id"));
}
}
} catch (Exception ex) {
LOG.warn("Error loading child forums for forum Id {}!", parent.getId(), ex);
}
}
@Override
public void save(Forum forum) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(INSERT_FORUM, RETURN_GENERATED_KEYS)) {
ps.setString(1, forum.getName());
ps.setInt(2, forum.getParent().getId());
ps.setInt(3, forum.getPost());
ps.setInt(4, forum.getType().ordinal());
ps.setInt(5, forum.getVisibility().ordinal());
ps.setInt(6, forum.getOwnerId());
ps.executeUpdate();
try (var rs = ps.getGeneratedKeys()) {
if (rs.next()) {
forum.setId(rs.getInt(1));
}
}
} catch (Exception ex) {
LOG.error("Error saving forum Id {} in database!", forum.getId(), ex);
}
}
}
@@ -0,0 +1,110 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository.impl;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.gameserver.bbs.model.Post;
import com.l2jserver.gameserver.bbs.model.Topic;
import com.l2jserver.gameserver.bbs.repository.PostRepository;
/**
* Post repository MySQL implementation.
* @author Zoey76
* @version 2.6.2.0
*/
public class PostRepositoryMySQLImpl implements PostRepository {
private static final Logger LOG = LoggerFactory.getLogger(PostRepositoryMySQLImpl.class);
private static final String INSERT_POST = "INSERT INTO posts (post_id,post_owner_name,post_ownerid,post_date,post_topic_id,post_forum_id,post_txt) values (?,?,?,?,?,?,?)";
private static final String DELETE_POST = "DELETE FROM posts WHERE post_forum_id=? AND post_topic_id=?";
private static final String SELECT_POSTS = "SELECT * FROM posts WHERE post_forum_id=? AND post_topic_id=? ORDER BY post_id";
private static final String UPDATE_POST = "UPDATE posts SET post_txt=? WHERE post_id=? AND post_topic_id=? AND post_forum_id=?";
public void save(Post cp) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(INSERT_POST)) {
ps.setInt(1, cp.getId());
ps.setString(2, cp.getOwnerName());
ps.setInt(3, cp.getOwnerId());
ps.setLong(4, cp.getDate());
ps.setInt(5, cp.getTopicId());
ps.setInt(6, cp.getForumId());
ps.setString(7, cp.getTxt());
ps.execute();
} catch (Exception ex) {
LOG.warn("Could not save post Id {} in database!", cp.getId(), ex);
}
}
@Override
public void delete(Topic topic) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(DELETE_POST)) {
ps.setInt(1, topic.getForumId());
ps.setInt(2, topic.getId());
ps.execute();
} catch (Exception ex) {
LOG.warn("Unable to delete post for topic Id {} in forum Id {} from database!", topic.getForumId(), topic.getId(), ex);
}
}
@Override
public List<Post> load(Topic topic) {
final var posts = new LinkedList<Post>();
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(SELECT_POSTS)) {
ps.setInt(1, topic.getForumId());
ps.setInt(2, topic.getId());
try (var rs = ps.executeQuery()) {
while (rs.next()) {
final Post cp = new Post(rs.getInt("post_id"), rs.getString("post_owner_name"), rs.getInt("post_ownerid"), //
rs.getLong("post_date"), rs.getInt("post_topic_id"), rs.getInt("post_forum_id"), rs.getString("post_txt"));
posts.add(cp);
}
}
} catch (Exception ex) {
LOG.warn("Unable to get post from topic Id {} in forum Id {} from database!", topic.getForumId(), topic.getId(), ex);
}
return posts;
}
@Override
public void update(Post cp) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(UPDATE_POST)) {
ps.setString(1, cp.getTxt());
ps.setInt(2, cp.getId());
ps.setInt(3, cp.getTopicId());
ps.setInt(4, cp.getForumId());
ps.execute();
} catch (Exception ex) {
LOG.warn("Unable to store post Id {} in database!", cp.getId(), ex);
}
}
}
@@ -0,0 +1,102 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.repository.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.commons.database.ConnectionFactory;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.Topic;
import com.l2jserver.gameserver.bbs.model.TopicType;
import com.l2jserver.gameserver.bbs.repository.TopicRepository;
import com.l2jserver.gameserver.bbs.service.TopicBBSManager;
/**
* Topic Repository MySQL implementation.
* @author Zoey76
* @version 2.6.2.0
*/
public class TopicRepositoryMySQLImpl implements TopicRepository {
private static final Logger LOG = LoggerFactory.getLogger(TopicRepositoryMySQLImpl.class);
private static final String SELECT_TOPICS = "SELECT * FROM topic WHERE topic_forum_id=? ORDER BY topic_id DESC";
private static final String DELETE_TOPIC = "DELETE FROM topic WHERE topic_id=? AND topic_forum_id=?";
private static final String INSERT_TOPIC = "INSERT INTO topic (topic_id,topic_forum_id,topic_name,topic_date,topic_ownername,topic_ownerid,topic_type,topic_reply) values (?,?,?,?,?,?,?,?)";
@Override
public void load(Forum forum) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(SELECT_TOPICS)) {
ps.setInt(1, forum.getId());
try (var rs = ps.executeQuery()) {
while (rs.next()) {
final var topic = new Topic(rs.getInt("topic_id"), //
rs.getInt("topic_forum_id"), rs.getString("topic_name"), rs.getLong("topic_date"), //
rs.getString("topic_ownername"), rs.getInt("topic_ownerid"), TopicType.values()[rs.getInt("topic_type")], //
rs.getInt("topic_reply"));
TopicBBSManager.getInstance().addTopic(topic);
forum.getTopics().put(topic.getId(), topic);
if (topic.getId() > TopicBBSManager.getInstance().getMaxId(forum)) {
TopicBBSManager.getInstance().setMaxId(topic.getId(), forum);
}
}
}
} catch (Exception ex) {
LOG.warn("Could not get from database topics for forum Id {}!", forum.getId(), ex);
}
}
@Override
public void save(Topic topic) {
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(INSERT_TOPIC)) {
ps.setInt(1, topic.getId());
ps.setInt(2, topic.getForumId());
ps.setString(3, topic.getName());
ps.setLong(4, topic.getDate());
ps.setString(5, topic.getOwnerName());
ps.setInt(6, topic.getOwnerId());
ps.setInt(7, topic.getType().ordinal());
ps.setInt(8, topic.getReply());
ps.execute();
} catch (Exception e) {
LOG.warn("Error while saving new Topic to database!", e);
}
}
@Override
public void delete(Topic topic, Forum forum) {
TopicBBSManager.getInstance().delTopic(topic);
forum.removeTopic(topic.getId());
try (var con = ConnectionFactory.getInstance().getConnection();
var ps = con.prepareStatement(DELETE_TOPIC)) {
ps.setInt(1, topic.getId());
ps.setInt(2, forum.getId());
ps.execute();
} catch (Exception e) {
LOG.warn("Error while deleting topic ID {} from database!", topic.getId(), e);
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.service;
import java.util.ArrayList;
import java.util.List;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.network.serverpackets.ShowBoard;
public abstract class BaseBBSManager {
public abstract void parsecmd(String command, L2PcInstance activeChar);
public abstract void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar);
protected void send1001(String html, L2PcInstance player) {
if (html.length() < 8192) {
player.sendPacket(new ShowBoard(html, "1001"));
}
}
protected void send1002(L2PcInstance player) {
send1002(player, " ", " ", "0");
}
protected void send1002(L2PcInstance activeChar, String string, String string2, String string3) {
List<String> arg = new ArrayList<>();
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add("0");
arg.add(activeChar.getName());
arg.add(Integer.toString(activeChar.getObjectId()));
arg.add(activeChar.getAccountName());
arg.add("9");
arg.add(string2); // subject?
arg.add(string2); // subject?
arg.add(string); // text
arg.add(string3); // date?
arg.add(string3); // date?
arg.add("0");
arg.add("0");
activeChar.sendPacket(new ShowBoard(arg));
}
}
@@ -0,0 +1,107 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.service;
import static com.l2jserver.gameserver.bbs.model.ForumType.CLAN;
import static com.l2jserver.gameserver.bbs.model.ForumVisibility.CLAN_MEMBER_ONLY;
import static com.l2jserver.gameserver.config.Configuration.general;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.ForumType;
import com.l2jserver.gameserver.bbs.model.ForumVisibility;
import com.l2jserver.gameserver.dao.factory.impl.DAOFactory;
import com.l2jserver.gameserver.model.L2Clan;
/**
* Forums BBS Manager.
* @author Zoey76
* @version 2.6.2.0
*/
public class ForumsBBSManager {
private static final Logger LOG = LoggerFactory.getLogger(ForumsBBSManager.class);
private static final Map<String, Forum> FORUMS_BY_NAME = new ConcurrentHashMap<>();
private static final Map<Integer, Forum> FORUMS_BY_ID = new ConcurrentHashMap<>();
protected ForumsBBSManager() {
// Do nothing.
}
public void load() {
FORUMS_BY_NAME.putAll(DAOFactory.getInstance().getForumRepository().getForums());
FORUMS_BY_ID.putAll(FORUMS_BY_NAME.values().stream().collect(Collectors.toMap(Forum::getId, f -> f)));
LOG.info("Loaded {} forums.", FORUMS_BY_NAME.size());
}
public Forum getForumByName(String name) {
return FORUMS_BY_NAME.get(name);
}
public Forum getForumById(Integer id) {
return FORUMS_BY_ID.get(id);
}
public Forum create(String name, Forum parent, ForumType type, ForumVisibility visibility, int ownerId) {
final var forum = new Forum(0, name, parent, type, visibility, ownerId);
parent.addChild(forum);
DAOFactory.getInstance().getForumRepository().save(forum);
FORUMS_BY_NAME.put(forum.getName(), forum);
FORUMS_BY_ID.put(forum.getId(), forum);
return forum;
}
public Forum load(int id, String name, Forum parent, ForumType type, ForumVisibility visibility, int ownerId) {
final var forum = new Forum(id, name, parent, type, visibility, ownerId);
parent.addChild(forum);
FORUMS_BY_NAME.put(forum.getName(), forum);
FORUMS_BY_ID.put(forum.getId(), forum);
return forum;
}
public void onClanLevel(L2Clan clan) {
if ((clan.getLevel() >= 2) && general().enableCommunityBoard()) {
final var clanRootForum = ForumsBBSManager.getInstance().getForumByName("ClanRoot");
if (clanRootForum != null) {
var forum = clanRootForum.getChildByName(clan.getName());
if (forum == null) {
ForumsBBSManager.getInstance().create(clan.getName(), clanRootForum, CLAN, CLAN_MEMBER_ONLY, clan.getId());
}
}
}
}
public static ForumsBBSManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
protected static final ForumsBBSManager INSTANCE = new ForumsBBSManager();
}
}
@@ -0,0 +1,186 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.service;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.ForumType;
import com.l2jserver.gameserver.bbs.model.Post;
import com.l2jserver.gameserver.bbs.model.Topic;
import com.l2jserver.gameserver.dao.factory.impl.DAOFactory;
import com.l2jserver.gameserver.handler.CommunityBoardHandler;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.util.StringUtil;
/**
* Post BBS Manager.
* @author Zoey76
* @version 2.6.2.0
*/
public class PostBBSManager extends BaseBBSManager {
private final Map<Topic, List<Post>> _postByTopic = new ConcurrentHashMap<>();
protected PostBBSManager() {
// Do nothing.
}
public List<Post> getGPostByTopic(Topic topic) {
var posts = _postByTopic.get(topic);
if (posts == null) {
posts = DAOFactory.getInstance().getPostRepository().load(topic);
_postByTopic.put(topic, posts);
}
return posts;
}
public void delPostByTopic(Topic t) {
_postByTopic.remove(t);
}
public void addPostByTopic(Topic topic, List<Post> posts) {
_postByTopic.putIfAbsent(topic, posts);
}
@Override
public void parsecmd(String command, L2PcInstance activeChar) {
if (command.startsWith("_bbsposts;read;")) {
final var st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final var forumId = Integer.parseInt(st.nextToken());
final var topicId = Integer.parseInt(st.nextToken());
final var index = st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 1;
final var forum = ForumsBBSManager.getInstance().getForumById(forumId);
final var topic = TopicBBSManager.getInstance().getTopicById(topicId);
showPost(topic, forum, activeChar, index);
} else if (command.startsWith("_bbsposts;edit;")) {
final var st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
final var forumId = Integer.parseInt(st.nextToken());
final var topicId = Integer.parseInt(st.nextToken());
final var postId = Integer.parseInt(st.nextToken());
final var forum = ForumsBBSManager.getInstance().getForumById(forumId);
final var topic = TopicBBSManager.getInstance().getTopicById(topicId);
showEditPost(topic, forum, activeChar, postId);
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showEditPost(Topic topic, Forum forum, L2PcInstance activeChar, int idp) {
if (topic == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This topic does not exist!</center></body></html>", activeChar);
} else {
final List<Post> p = getGPostByTopic(topic);
if ((forum == null) || (p == null)) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This forum or post does not exist!</center></body></html>", activeChar);
} else {
showHtmlEditPost(topic, activeChar, forum, p);
}
}
}
private void showPost(Topic topic, Forum forum, L2PcInstance activeChar, int ind) {
if ((forum == null) || (topic == null)) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>Error: This forum is not implemented yet!</center></body></html>", activeChar);
} else if (forum.getType() == ForumType.MEMO) {
showMemoPost(topic, activeChar, forum);
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>The forum: " + forum.getName() + " is not implemented yet!</center></body></html>", activeChar);
}
}
private void showHtmlEditPost(Topic topic, L2PcInstance activeChar, Forum forum, List<Post> posts) {
final String html = StringUtil.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">", forum
.getName(), " Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0><tr><td width=610><img src=\"sek.cbui355\" width=\"610\" height=\"1\"><br1><img src=\"sek.cbui355\" width=\"610\" height=\"1\"></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=20></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&$413;</td><td FIXWIDTH=540>", topic
.getName(), "</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29 valign=top>&$427;</td><td align=center FIXWIDTH=540><MultiEdit var =\"Content\" width=535 height=313></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&nbsp;</td><td align=center FIXWIDTH=70><button value=\"&$140;\" action=\"Write Post ", String
.valueOf(forum.getId()), ";", String.valueOf(topic
.getId()), ";0 _ Content Content Content\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td><td align=center FIXWIDTH=70><button value = \"&$141;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td><td align=center FIXWIDTH=400>&nbsp;</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table></center></body></html>");
send1001(html, activeChar);
send1002(activeChar, posts.get(0).getTxt(), topic.getName(), DateFormat.getInstance().format(new Date(topic.getDate())));
}
private void showMemoPost(Topic topic, L2PcInstance activeChar, Forum forum) {
final var posts = getGPostByTopic(topic);
Locale locale = Locale.getDefault();
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
String mes = posts.get(0).getTxt().replace(">", "&gt;");
mes = mes.replace("<", "&lt;");
// TODO(Zoey76): Replace this with a template.
final String html = StringUtil
.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0 bgcolor=333333><tr><td height=10></td></tr><tr><td fixWIDTH=55 align=right valign=top>&$413; : &nbsp;</td><td fixWIDTH=380 valign=top>", topic
.getName(), "</td><td fixwidth=5></td><td fixwidth=50></td><td fixWIDTH=120></td></tr><tr><td height=10></td></tr><tr><td align=right><font color=\"AAAAAA\" >&$417; : &nbsp;</font></td><td><font color=\"AAAAAA\">", topic.getOwnerName()
+ "</font></td><td></td><td><font color=\"AAAAAA\">&$418; :</font></td><td><font color=\"AAAAAA\">", dateFormat.format(posts.get(0)
.getDate()), "</font></td></tr><tr><td height=10></td></tr></table><br><table border=0 cellspacing=0 cellpadding=0><tr><td fixwidth=5></td><td FIXWIDTH=600 align=left>", mes, "</td><td fixqqwidth=5></td></tr></table><br><img src=\"L2UI.squareblank\" width=\"1\" height=\"5\"><img src=\"L2UI.squaregray\" width=\"610\" height=\"1\"><img src=\"L2UI.squareblank\" width=\"1\" height=\"5\"><table border=0 cellspacing=0 cellpadding=0 FIXWIDTH=610><tr><td width=50><button value=\"&$422;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td><td width=560 align=right><table border=0 cellspacing=0><tr><td FIXWIDTH=300></td><td><button value = \"&$424;\" action=\"bypass _bbsposts;edit;", String
.valueOf(forum.getId()), ";", String.valueOf(topic.getId()), ";0\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;<td><button value = \"&$425;\" action=\"bypass _bbstopics;del;", String.valueOf(forum.getId()), ";", String
.valueOf(topic.getId()), "\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;<td><button value = \"&$421;\" action=\"bypass _bbstopics;crea;", String
.valueOf(forum.getId()), "\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td>&nbsp;</tr></table></td></tr></table><br><br><br></center></body></html>");
CommunityBoardHandler.separateAndSend(html, activeChar);
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar) {
StringTokenizer st = new StringTokenizer(ar1, ";");
int idf = Integer.parseInt(st.nextToken());
int idt = Integer.parseInt(st.nextToken());
int idp = Integer.parseInt(st.nextToken());
Forum f = ForumsBBSManager.getInstance().getForumById(idf);
if (f == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " does not exist !</center><br><br></body></html>", activeChar);
} else {
Topic t = f.getTopic(idt);
if (t == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + idt + " does not exist !</center><br><br></body></html>", activeChar);
} else {
final var posts = getGPostByTopic(t);
if (!posts.isEmpty()) {
final Post post = posts.get(idp);
if (post == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the post: " + idp + " does not exist !</center><br><br></body></html>", activeChar);
} else {
post.setTxt(ar4);
DAOFactory.getInstance().getPostRepository().update(post);
parsecmd("_bbsposts;read;" + f.getId() + ";" + t.getId(), activeChar);
}
}
}
}
}
public static PostBBSManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
protected static final PostBBSManager INSTANCE = new PostBBSManager();
}
}
@@ -0,0 +1,278 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.bbs.service;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import com.l2jserver.gameserver.bbs.model.Forum;
import com.l2jserver.gameserver.bbs.model.ForumType;
import com.l2jserver.gameserver.bbs.model.Post;
import com.l2jserver.gameserver.bbs.model.Topic;
import com.l2jserver.gameserver.bbs.model.TopicType;
import com.l2jserver.gameserver.dao.factory.impl.DAOFactory;
import com.l2jserver.gameserver.data.sql.impl.ClanTable;
import com.l2jserver.gameserver.handler.CommunityBoardHandler;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.util.StringUtil;
/**
* Topic BBS Manager.
* @author Zoey76
* @version 2.6.2.0
*/
public class TopicBBSManager extends BaseBBSManager {
private final List<Topic> table = new CopyOnWriteArrayList<>();
private final Map<Forum, Integer> maxId = new HashMap<>();
protected TopicBBSManager() {
// Do nothing.
}
public void addTopic(Topic topic) {
table.add(topic);
}
public void delTopic(Topic topic) {
table.remove(topic);
}
public void setMaxId(int id, Forum forum) {
maxId.put(forum, id);
}
public int getMaxId(Forum f) {
var i = maxId.get(f);
if (i == null) {
return 0;
}
return i;
}
public Topic getTopicById(int id) {
for (var topic : table) {
if (topic.getId() == id) {
return topic;
}
}
return null;
}
@Override
public void parsewrite(String ar1, String ar2, String ar3, String ar4, String ar5, L2PcInstance activeChar) {
if (ar1.equals("crea")) {
final var forum = ForumsBBSManager.getInstance().getForumById(Integer.parseInt(ar2));
if (forum == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + ar2 + " is not implemented yet</center><br><br></body></html>", activeChar);
return;
}
final var id = TopicBBSManager.getInstance().getMaxId(forum) + 1;
final var date = Calendar.getInstance().getTimeInMillis();
final var topic = new Topic(id, Integer.parseInt(ar2), ar5, date, activeChar.getName(), activeChar.getObjectId(), TopicType.MEMO, 0);
TopicBBSManager.getInstance().addTopic(topic);
DAOFactory.getInstance().getTopicRepository().save(topic);
forum.addTopic(topic);
TopicBBSManager.getInstance().setMaxId(topic.getId(), forum);
final var posts = new LinkedList<Post>();
posts.add(new Post(0, activeChar.getName(), activeChar.getObjectId(), Calendar.getInstance().getTimeInMillis(), topic.getId(), forum.getId(), ar4));
PostBBSManager.getInstance().addPostByTopic(topic, posts);
parsecmd("_bbsmemo", activeChar);
} else if (ar1.equals("del")) {
final var forum = ForumsBBSManager.getInstance().getForumById(Integer.parseInt(ar2));
if (forum == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + ar2 + " does not exist !</center><br><br></body></html>", activeChar);
return;
}
final var topic = forum.getTopic(Integer.parseInt(ar3));
if (topic == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + ar3 + " does not exist !</center><br><br></body></html>", activeChar);
return;
}
final var posts = PostBBSManager.getInstance().getGPostByTopic(topic);
if (!posts.isEmpty()) {
PostBBSManager.getInstance().delPostByTopic(topic);
DAOFactory.getInstance().getPostRepository().delete(topic);
}
DAOFactory.getInstance().getTopicRepository().delete(topic, forum);
parsecmd("_bbsmemo", activeChar);
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + ar1 + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
@Override
public void parsecmd(String command, L2PcInstance activeChar) {
if (command.equals("_bbsmemo")) {
showTopics(activeChar.getMemo(), activeChar, 1, activeChar.getMemo().getId());
} else if (command.startsWith("_bbstopics;read")) {
StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
int idf = Integer.parseInt(st.nextToken());
String index = null;
if (st.hasMoreTokens()) {
index = st.nextToken();
}
int ind;
if (index == null) {
ind = 1;
} else {
ind = Integer.parseInt(index);
}
showTopics(ForumsBBSManager.getInstance().getForumById(idf), activeChar, ind, idf);
} else if (command.startsWith("_bbstopics;crea")) {
StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
int idf = Integer.parseInt(st.nextToken());
showNewTopic(ForumsBBSManager.getInstance().getForumById(idf), activeChar, idf);
} else if (command.startsWith("_bbstopics;del")) {
StringTokenizer st = new StringTokenizer(command, ";");
st.nextToken();
st.nextToken();
int idf = Integer.parseInt(st.nextToken());
int idt = Integer.parseInt(st.nextToken());
Forum f = ForumsBBSManager.getInstance().getForumById(idf);
if (f == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " does not exist !</center><br><br></body></html>", activeChar);
} else {
Topic t = f.getTopic(idt);
if (t == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the topic: " + idt + " does not exist !</center><br><br></body></html>", activeChar);
} else {
final var p = PostBBSManager.getInstance().getGPostByTopic(t);
if (p != null) {
PostBBSManager.getInstance().delPostByTopic(t);
DAOFactory.getInstance().getPostRepository().delete(t);
}
DAOFactory.getInstance().getTopicRepository().delete(t, f);
parsecmd("_bbsmemo", activeChar);
}
}
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the command: " + command + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showNewTopic(Forum forum, L2PcInstance activeChar, int idf) {
if (forum == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " is not implemented yet</center><br><br></body></html>", activeChar);
} else if (forum.getType() == ForumType.MEMO) {
showMemoNewTopics(forum, activeChar);
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showMemoNewTopics(Forum forum, L2PcInstance activeChar) {
final String html = StringUtil
.concat("<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=0><tr><td width=610><img src=\"sek.cbui355\" width=\"610\" height=\"1\"><br1><img src=\"sek.cbui355\" width=\"610\" height=\"1\"></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=20></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&$413;</td><td FIXWIDTH=540><edit var = \"Title\" width=540 height=13></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29 valign=top>&$427;</td><td align=center FIXWIDTH=540><MultiEdit var =\"Content\" width=535 height=313></td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr></table><table fixwidth=610 border=0 cellspacing=0 cellpadding=0><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=1></td><td align=center FIXWIDTH=60 height=29>&nbsp;</td><td align=center FIXWIDTH=70><button value=\"&$140;\" action=\"Write Topic crea ", String
.valueOf(forum
.getId()), " Title Content Title\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td><td align=center FIXWIDTH=70><button value = \"&$141;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td><td align=center FIXWIDTH=400>&nbsp;</td><td><img src=\"l2ui.mini_logo\" width=5 height=1></td></tr></table></center></body></html>");
send1001(html, activeChar);
send1002(activeChar);
}
private void showTopics(Forum forum, L2PcInstance activeChar, int index, int idf) {
if (forum == null) {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + idf + " is not implemented yet</center><br><br></body></html>", activeChar);
} else if (forum.getType() == ForumType.MEMO) {
showMemoTopics(forum, activeChar, index);
} else {
CommunityBoardHandler.separateAndSend("<html><body><br><br><center>the forum: " + forum.getName() + " is not implemented yet</center><br><br></body></html>", activeChar);
}
}
private void showMemoTopics(Forum forum, L2PcInstance activeChar, int index) {
final StringBuilder html = StringUtil
.startAppend(2000, "<html><body><br><br><table border=0 width=610><tr><td width=10></td><td width=600 align=left><a action=\"bypass _bbshome\">HOME</a>&nbsp;>&nbsp;<a action=\"bypass _bbsmemo\">Memo Form</a></td></tr></table><img src=\"L2UI.squareblank\" width=\"1\" height=\"10\"><center><table border=0 cellspacing=0 cellpadding=2 bgcolor=888888 width=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415 align=center>&$413;</td><td FIXWIDTH=120 align=center></td><td FIXWIDTH=70 align=center>&$418;</td></tr></table>");
final DateFormat dateFormat = DateFormat.getInstance();
for (int i = 0, j = getMaxId(forum) + 1; i < (12 * index); j--) {
if (j < 0) {
break;
}
Topic t = forum.getTopic(j);
if (t != null) {
if (i++ >= (12 * (index - 1))) {
StringUtil.append(html, "<table border=0 cellspacing=0 cellpadding=5 WIDTH=610><tr><td FIXWIDTH=5></td><td FIXWIDTH=415><a action=\"bypass _bbsposts;read;", String.valueOf(forum.getId()), ";", String.valueOf(t.getId()), "\">", t
.getName(), "</a></td><td FIXWIDTH=120 align=center></td><td FIXWIDTH=70 align=center>", dateFormat.format(new Date(t.getDate())), "</td></tr></table><img src=\"L2UI.Squaregray\" width=\"610\" height=\"1\">");
}
}
}
html.append("<br><table width=610 cellspace=0 cellpadding=0><tr><td width=50><button value=\"&$422;\" action=\"bypass _bbsmemo\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"></td><td width=510 align=center><table border=0><tr>");
if (index == 1) {
html.append("<td><button action=\"\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
} else {
StringUtil.append(html, "<td><button action=\"bypass _bbstopics;read;", String.valueOf(forum.getId()), ";", String.valueOf(index - 1), "\" back=\"l2ui_ch3.prev1_down\" fore=\"l2ui_ch3.prev1\" width=16 height=16 ></td>");
}
int nbp;
nbp = forum.getTopicSize() / 8;
if ((nbp * 8) != ClanTable.getInstance().getClanCount()) {
nbp++;
}
for (int i = 1; i <= nbp; i++) {
if (i == index) {
StringUtil.append(html, "<td> ", String.valueOf(i), " </td>");
} else {
StringUtil.append(html, "<td><a action=\"bypass _bbstopics;read;", String.valueOf(forum.getId()), ";", String.valueOf(i), "\"> ", String.valueOf(i), " </a></td>");
}
}
if (index == nbp) {
html.append("<td><button action=\"\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
} else {
StringUtil.append(html, "<td><button action=\"bypass _bbstopics;read;", String.valueOf(forum.getId()), ";", String.valueOf(index + 1), "\" back=\"l2ui_ch3.next1_down\" fore=\"l2ui_ch3.next1\" width=16 height=16 ></td>");
}
StringUtil.append(html, "</tr></table> </td> <td align=right><button value = \"&$421;\" action=\"bypass _bbstopics;crea;", String.valueOf(forum.getId()), //
"\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\" ></td></tr><tr><td><img src=\"l2ui.mini_logo\" width=5 height=10></td></tr><tr> <td></td><td align=center><table border=0><tr><td></td><td><edit var = \"Search\" width=130 height=11></td><td><button value=\"&$420;\" action=\"Write 5 -2 0 Search _ _\" back=\"l2ui_ch3.smallbutton2_down\" width=65 height=20 fore=\"l2ui_ch3.smallbutton2\"> </td> </tr></table> </td></tr></table><br><br><br></center></body></html>");
CommunityBoardHandler.separateAndSend(html.toString(), activeChar);
}
public static TopicBBSManager getInstance() {
return SingletonHolder._instance;
}
private static class SingletonHolder {
protected static final TopicBBSManager _instance = new TopicBBSManager();
}
}
@@ -0,0 +1,169 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.cache;
import static com.l2jserver.gameserver.config.Configuration.general;
import static com.l2jserver.gameserver.config.Configuration.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.l2jserver.gameserver.util.file.filter.HTMLFilter;
/**
* HTML Cache.
* @author Layane
* @author Zoey76
*/
public class HtmCache {
private static final Logger LOG = LoggerFactory.getLogger(HtmCache.class);
private static final HTMLFilter HTML_FILTER = new HTMLFilter();
private static final Map<String, String> HTML_CACHE = general().lazyCache() ? new ConcurrentHashMap<>() : new HashMap<>();
private int _loadedFiles;
private long _bytesBuffLen;
protected HtmCache() {
reload();
}
public void reload() {
reload(server().getDatapackRoot());
}
public void reload(File f) {
if (!general().lazyCache()) {
LOG.info("Html cache start...");
parseDir(f);
LOG.info(String.format("%.3f", getMemoryUsage()) + " megabytes on " + getLoadedFiles() + " files loaded.");
} else {
HTML_CACHE.clear();
_loadedFiles = 0;
_bytesBuffLen = 0;
LOG.info("Running lazy cache.");
}
}
public void reloadPath(File f) {
parseDir(f);
LOG.info("Reloaded specified path.");
}
public double getMemoryUsage() {
return ((float) _bytesBuffLen / 1048576);
}
public int getLoadedFiles() {
return _loadedFiles;
}
private void parseDir(File dir) {
final File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (!file.isDirectory()) {
loadFile(file);
} else {
parseDir(file);
}
}
}
}
public String loadFile(File file) {
if (!HTML_FILTER.accept(file)) {
return null;
}
String content = null;
try (var fis = new FileInputStream(file);
var bis = new BufferedInputStream(fis)) {
final int bytes = bis.available();
byte[] raw = new byte[bytes];
bis.read(raw);
content = new String(raw, UTF_8);
content = content.replaceAll("(?s)<!--.*?-->", ""); // Remove html comments
String oldContent = HTML_CACHE.put(file.getCanonicalPath(), content);
if (oldContent == null) {
_bytesBuffLen += bytes;
_loadedFiles++;
} else {
_bytesBuffLen = (_bytesBuffLen - oldContent.length()) + bytes;
}
} catch (Exception e) {
LOG.warn("Problem with htm file {}!", file, e);
}
return content;
}
public String getHtm(String prefix, String path) {
final var newPath = Objects.requireNonNullElse(prefix, "") + path;
var content = HTML_CACHE.get(newPath);
if (general().lazyCache() && (content == null)) {
content = loadFile(new File(server().getDatapackRoot(), newPath));
if (content == null) {
content = loadFile(new File(server().getScriptRoot(), newPath));
}
// If multilanguage content is not present, try default location.
if ((prefix != null) && (content == null)) {
content = loadFile(new File(server().getDatapackRoot(), path));
if (content == null) {
content = loadFile(new File(server().getScriptRoot(), path));
}
}
}
return content;
}
public boolean contains(String path) {
return HTML_CACHE.containsKey(path);
}
/**
* @param path The path to the HTM
* @return {@code true} if the path targets a HTM or HTML file, {@code false} otherwise.
*/
public boolean isLoadable(String path) {
return HTML_FILTER.accept(new File(server().getDatapackRoot(), path));
}
public static HtmCache getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
protected static final HtmCache INSTANCE = new HtmCache();
}
}
@@ -0,0 +1,68 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.cache;
import static com.l2jserver.gameserver.config.Configuration.general;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
/**
* @author -Nemesiss-
*/
public class WarehouseCacheManager {
protected final Map<L2PcInstance, Long> _cachedWh = new ConcurrentHashMap<>();
protected WarehouseCacheManager() {
ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new CacheScheduler(), 120000, 60000);
}
public void addCacheTask(L2PcInstance pc) {
_cachedWh.put(pc, System.currentTimeMillis());
}
public void remCacheTask(L2PcInstance pc) {
_cachedWh.remove(pc);
}
public class CacheScheduler implements Runnable {
@Override
public void run() {
long cTime = System.currentTimeMillis();
for (L2PcInstance pc : _cachedWh.keySet()) {
if ((cTime - _cachedWh.get(pc)) > general().getWarehouseCacheTime()) {
pc.clearWarehouse();
_cachedWh.remove(pc);
}
}
}
}
public static WarehouseCacheManager getInstance() {
return SingletonHolder._instance;
}
private static class SingletonHolder {
protected static final WarehouseCacheManager _instance = new WarehouseCacheManager();
}
}
@@ -0,0 +1,89 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Seconds2MillisecondsConverter;
import com.l2jserver.gameserver.model.zone.ZoneId;
/**
* Buffer Service Configuration.
* @author HorridoJoho
* @version 2.6.2.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/bufferservice.properties",
"file:./config/bufferservice.properties",
"classpath:config/bufferservice.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface BufferServiceConfiguration extends Reloadable {
@Key("Enable")
boolean enable();
@Key("HealCooldown")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getHealCooldown();
@Key("MaxUniqueLists")
int getMaxUniqueLists();
@Key("Debug")
boolean getDebug();
@Key("ForbidInZones")
ZoneId[] getForbidInZones();
@Key("ForbidInEvents")
boolean getForbidInEvents();
@Key("ForbidInDuel")
boolean getForbidInDuel();
@Key("ForbidInFight")
boolean getForbidInFight();
@Key("ForbidInPvp")
boolean getForbidInPvp();
@Key("ForbidForChaoticPlayers")
boolean getForbidForChaoticPlayers();
@Key("VoicedEnable")
boolean getVoicedEnable();
@Key("VoicedCommand")
String getVoicedCommand();
@Key("VoicedName")
String getVoicedName();
@Key("VoicedRequiredItem")
int getVoicedRequiredItem();
}
@@ -0,0 +1,129 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Castle Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/castle.properties",
"file:./config/castle.properties",
"classpath:config/castle.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 1, unit = HOURS, type = ASYNC)
public interface CastleConfiguration extends Reloadable {
@Key("SiegeHourList")
List<Integer> getSiegeHourList();
@Key("FunctionFeeDay7")
long getFunctionFeeDay7();
@Key("TeleportFunctionFeeLvl11")
int getTeleportFunctionFeeLvl11();
@Key("TeleportFunctionFeeLvl12")
int getTeleportFunctionFeeLvl12();
@Key("SupportFeeLvl15")
int getSupportFeeLvl15();
@Key("SupportFeeLvl18")
int getSupportFeeLvl18();
@Key("MpRegenerationFeeLvl18")
int getMpRegenerationFeeLvl18();
@Key("MpRegenerationFeeLvl20")
int getMpRegenerationFeeLvl20();
@Key("HpRegenerationFeeLvl25")
int getHpRegenerationFeeLvl25();
@Key("HpRegenerationFeeLvl30")
int getHpRegenerationFeeLvl30();
@Key("ExpRegenerationFeeLvl19")
int getExpRegenerationFeeLvl19();
@Key("ExpRegenerationFeeLvl20")
int getExpRegenerationFeeLvl20();
@Key("OuterDoorUpgradePriceLvl2")
int getOuterDoorUpgradePriceLvl2();
@Key("OuterDoorUpgradePriceLvl3")
int getOuterDoorUpgradePriceLvl3();
@Key("OuterDoorUpgradePriceLvl5")
int getOuterDoorUpgradePriceLvl5();
@Key("InnerDoorUpgradePriceLvl2")
int getInnerDoorUpgradePriceLvl2();
@Key("InnerDoorUpgradePriceLvl3")
int getInnerDoorUpgradePriceLvl3();
@Key("InnerDoorUpgradePriceLvl5")
int getInnerDoorUpgradePriceLvl5();
@Key("WallUpgradePriceLvl2")
int getWallUpgradePriceLvl2();
@Key("WallUpgradePriceLvl3")
int getWallUpgradePriceLvl3();
@Key("WallUpgradePriceLvl5")
int getWallUpgradePriceLvl5();
@Key("TrapUpgradePriceLvl1")
int getTrapUpgradePriceLvl1();
@Key("TrapUpgradePriceLvl2")
int getTrapUpgradePriceLvl2();
@Key("TrapUpgradePriceLvl3")
int getTrapUpgradePriceLvl3();
@Key("TrapUpgradePriceLvl4")
int getTrapUpgradePriceLvl4();
@Key("AllowRideWyvernAlways")
boolean allowRideWyvernAlways();
@Key("AllowRideWyvernDuringSiege")
boolean allowRideWyvernDuringSiege();
@Key("MpBuffFree")
boolean mpBuffFree();
}
@@ -0,0 +1,663 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.ClassMasterSetting;
import com.l2jserver.gameserver.config.converter.ClassMasterSettingConverter;
import com.l2jserver.gameserver.config.converter.Days2MillisecondsConverter;
import com.l2jserver.gameserver.config.converter.MapIntegerIntegerConverter;
import com.l2jserver.gameserver.config.converter.PatternConverter;
import com.l2jserver.gameserver.config.converter.Seconds2MillisecondsConverter;
/**
* Character Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/character.properties",
"file:./config/character.properties",
"classpath:config/character.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 5, unit = MINUTES, type = ASYNC)
public interface CharacterConfiguration extends Reloadable {
// Statistics
@Key("Delevel")
boolean delevel();
@Key("DecreaseSkillOnDelevel")
boolean decreaseSkillOnDelevel();
@Key("WeightLimit")
int getWeightLimit();
@Key("RunSpeedBoost")
int getRunSpeedBoost();
@Key("DeathPenaltyChance")
int getDeathPenaltyChance();
@Key("RespawnRestoreCP")
double getRespawnRestoreCP();
@Key("RespawnRestoreHP")
double getRespawnRestoreHP();
@Key("RespawnRestoreMP")
double getRespawnRestoreMP();
@Key("HpRegenMultiplier")
double getHpRegenMultiplier();
@Key("MpRegenMultiplier")
double getMpRegenMultiplier();
@Key("CpRegenMultiplier")
double getCpRegenMultiplier();
// Skills & Effects
@Key("ModifySkillDuration")
boolean modifySkillDuration();
@Key("SkillDuration")
@ConverterClass(MapIntegerIntegerConverter.class)
Map<Integer, Integer> getSkillDuration();
@Key("ModifySkillReuse")
boolean modifySkillReuse();
@Key("SkillReuse")
@ConverterClass(MapIntegerIntegerConverter.class)
Map<Integer, Integer> getSkillReuse();
@Key("AutoLearnSkills")
boolean autoLearnSkills();
@Key("AutoLearnForgottenScrollSkills")
boolean autoLearnForgottenScrollSkills();
@Key("AutoLootHerbs")
boolean autoLootHerbs();
@Key("MaxBuffAmount")
int getMaxBuffAmount();
@Key("MaxTriggeredBuffAmount")
int getMaxTriggeredBuffAmount();
@Key("MaxDanceAmount")
int getMaxDanceAmount();
@Key("DanceCancelBuff")
boolean danceCancelBuff();
@Key("DanceConsumeAdditionalMP")
boolean danceConsumeAdditionalMP();
@Key("StoreDances")
boolean storeDances();
@Key("AutoLearnDivineInspiration")
boolean autoLearnDivineInspiration();
@Key("CancelByHit")
String cancelByHit();
default boolean cancelBow() {
return cancelByHit().equalsIgnoreCase("all") || cancelByHit().equalsIgnoreCase("bow");
}
default boolean cancelCast() {
return cancelByHit().equalsIgnoreCase("all") || cancelByHit().equalsIgnoreCase("cast");
}
@Key("MagicFailures")
boolean magicFailures();
@Key("PlayerFakeDeathUpProtection")
int getPlayerFakeDeathUpProtection();
@Key("StoreSkillCooltime")
boolean storeSkillCooltime();
@Key("SubclassStoreSkillCooltime")
boolean subclassStoreSkillCooltime();
@Key("ShieldBlocks")
boolean shieldBlocks();
@Key("PerfectShieldBlockRate")
int getPerfectShieldBlockRate();
@Key("EffectTickRatio")
int getEffectTickRatio();
// Class, Sub-class and skill learning
@Key("AllowClassMasters")
boolean allowClassMasters();
@Key("ConfigClassMaster")
@ConverterClass(ClassMasterSettingConverter.class)
ClassMasterSetting getClassMaster();
@Key("AllowEntireTree")
boolean allowEntireTree();
@Key("AlternateClassMaster")
boolean alternateClassMaster();
@Key("LifeCrystalNeeded")
boolean lifeCrystalNeeded();
@Key("EnchantSkillSpBookNeeded")
boolean enchantSkillSpBookNeeded();
@Key("DivineInspirationSpBookNeeded")
boolean divineInspirationSpBookNeeded();
@Key("SkillLearn")
boolean skillLearn();
@Key("SubclassWithoutQuests")
boolean subclassWithoutQuests();
@Key("SubclassEverywhere")
boolean subclassEverywhere();
@Key("TransformationWithoutQuest")
boolean transformationWithoutQuest();
@Key("FeeDeleteTransferSkills")
int getFeeDeleteTransferSkills();
@Key("FeeDeleteSubClassSkills")
int getFeeDeleteSubClassSkills();
// Summons
@Key("SummonStoreSkillCooltime")
boolean summonStoreSkillCooltime();
@Key("RestoreServitorOnReconnect")
boolean restoreServitorOnReconnect();
@Key("RestorePetOnReconnect")
boolean restorePetOnReconnect();
// Limits
@Key("MaxExpBonus")
double getMaxExpBonus();
@Key("MaxSpBonus")
double getMaxSpBonus();
@Key("MaxRunSpeed")
int getMaxRunSpeed();
@Key("MaxPCritRate")
int getMaxPCritRate();
@Key("MaxMCritRate")
int getMaxMCritRate();
@Key("MaxPAtkSpeed")
int getMaxPAtkSpeed();
@Key("MaxMAtkSpeed")
int getMaxMAtkSpeed();
@Key("MaxEvasion")
int getMaxEvasion();
@Key("MinAbnormalStateSuccessRate")
int getMinAbnormalStateSuccessRate();
@Key("MaxAbnormalStateSuccessRate")
int getMaxAbnormalStateSuccessRate();
@Key("MaxPlayerLevel")
int getMaxPlayerLevel();
@Key("MaxPetLevel")
int getMaxPetLevel();
@Key("MaxSubclass")
int getMaxSubclass();
@Key("BaseSubclassLevel")
int getBaseSubclassLevel();
@Key("MaxSubclassLevel")
int getMaxSubclassLevel();
@Key("MaxPvtStoreSellSlotsDwarf")
int getMaxPvtStoreSellSlotsDwarf();
@Key("MaxPvtStoreSellSlotsOther")
int getMaxPvtStoreSellSlotsOther();
@Key("MaxPvtStoreBuySlotsDwarf")
int getMaxPvtStoreBuySlotsDwarf();
@Key("MaxPvtStoreBuySlotsOther")
int getMaxPvtStoreBuySlotsOther();
@Key("MaximumSlotsForNoDwarf")
int getMaximumSlotsForNoDwarf();
@Key("MaximumSlotsForDwarf")
int getMaximumSlotsForDwarf();
@Key("MaximumSlotsForGMPlayer")
int getMaximumSlotsForGMPlayer();
@Key("MaximumSlotsForQuestItems")
int getMaximumSlotsForQuestItems();
@Key("MaximumWarehouseSlotsForDwarf")
int getMaximumWarehouseSlotsForDwarf();
@Key("MaximumWarehouseSlotsForNoDwarf")
int getMaximumWarehouseSlotsForNoDwarf();
@Key("MaximumWarehouseSlotsForClan")
int getMaximumWarehouseSlotsForClan();
@Key("MaximumFreightSlots")
int getMaximumFreightSlots();
@Key("FreightPrice")
int getFreightPrice();
@Key("NpcTalkBlockingTime")
int getNpcTalkBlockingTime();
@Key("FriendListLimit")
int getFriendListLimit();
@Key("BlockListLimit")
int getBlockListLimit();
// Enchanting
@Key("EnchantChanceElementStone")
int getEnchantChanceElementStone();
@Key("EnchantChanceElementCrystal")
int getEnchantChanceElementCrystal();
@Key("EnchantChanceElementJewel")
int getEnchantChanceElementJewel();
@Key("EnchantChanceElementEnergy")
int getEnchantChanceElementEnergy();
@Key("EnchantBlacklist")
Set<Integer> getEnchantBlacklist();
// Augmenting
@Key("AugmentationNGSkillChance")
int getAugmentationNGSkillChance();
@Key("AugmentationMidSkillChance")
int getAugmentationMidSkillChance();
@Key("AugmentationHighSkillChance")
int getAugmentationHighSkillChance();
@Key("AugmentationTopSkillChance")
int getAugmentationTopSkillChance();
@Key("AugmentationAccSkillChance")
int getAugmentationAccSkillChance();
@Key("AugmentationBaseStatChance")
int getAugmentationBaseStatChance();
@Key("AugmentationNGGlowChance")
int getAugmentationNGGlowChance();
@Key("AugmentationMidGlowChance")
int getAugmentationMidGlowChance();
@Key("AugmentationHighGlowChance")
int getAugmentationHighGlowChance();
@Key("AugmentationTopGlowChance")
int getAugmentationTopGlowChance();
@Key("RetailLikeAugmentation")
boolean retailLikeAugmentation();
@Key("RetailLikeAugmentationNoGradeChance")
List<Integer> getRetailLikeAugmentationNoGradeChance();
@Key("RetailLikeAugmentationMidGradeChance")
List<Integer> getRetailLikeAugmentationMidGradeChance();
@Key("RetailLikeAugmentationHighGradeChance")
List<Integer> getRetailLikeAugmentationHighGradeChance();
@Key("RetailLikeAugmentationTopGradeChance")
List<Integer> getRetailLikeAugmentationTopGradeChance();
@Key("RetailLikeAugmentationAccessory")
boolean retailLikeAugmentationAccessory();
@Key("AugmentationBlacklist")
Set<Integer> getAugmentationBlacklist();
@Key("AllowAugmentPvPItems")
boolean allowAugmentPvPItems();
// Karma
@Key("KarmaPlayerCanBeKilledInPeaceZone")
boolean karmaPlayerCanBeKilledInPeaceZone();
@Key("KarmaPlayerCanUseGK")
boolean karmaPlayerCanUseGK();
@Key("KarmaPlayerCanTeleport")
boolean karmaPlayerCanTeleport();
@Key("KarmaPlayerCanShop")
boolean karmaPlayerCanShop();
@Key("KarmaPlayerCanTrade")
boolean karmaPlayerCanTrade();
@Key("KarmaPlayerCanUseWareHouse")
boolean karmaPlayerCanUseWareHouse();
// Fame
@Key("MaxPersonalFamePoints")
int getMaxPersonalFamePoints();
@Key("FortressZoneFameTaskFrequency")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getFortressZoneFameTaskFrequency();
@Key("FortressZoneFameAcquirePoints")
int getFortressZoneFameAcquirePoints();
@Key("CastleZoneFameTaskFrequency")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getCastleZoneFameTaskFrequency();
@Key("CastleZoneFameAcquirePoints")
int getCastleZoneFameAcquirePoints();
@Key("FameForDeadPlayers")
boolean fameForDeadPlayers();
// Crafting
@Key("Crafting")
boolean crafting();
@Key("CraftMasterwork")
boolean craftMasterwork();
@Key("DwarfRecipeLimit")
int getDwarfRecipeLimit();
@Key("CommonRecipeLimit")
int getCommonRecipeLimit();
@Key("AlternativeCrafting")
boolean alternativeCrafting();
@Key("CraftingSpeed")
double getCraftingSpeed();
@Key("CraftingXpRate")
double getCraftingXpRate();
@Key("CraftingSpRate")
double getCraftingSpRate();
@Key("CraftingRareXpRate")
double getCraftingRareXpRate();
@Key("CraftingRareSpRate")
double getCraftingRareSpRate();
@Key("BlacksmithUseRecipes")
boolean blacksmithUseRecipes();
@Key("StoreRecipeShopList")
boolean storeRecipeShopList();
// Clan
@Key("ClanLeaderDateChange")
int getClanLeaderDateChange();
@Key("ClanLeaderHourChange")
String getClanLeaderHourChange();
@Key("ClanLeaderInstantActivation")
boolean clanLeaderInstantActivation();
@Key("DaysBeforeJoinAClan")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeJoinAClan();
@Key("DaysBeforeCreateAClan")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeCreateAClan();
@Key("DaysToPassToDissolveAClan")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysToPassToDissolveAClan();
@Key("DaysBeforeJoiningAllianceAfterLeaving")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeJoiningAllianceAfterLeaving();
@Key("DaysBeforeJoinAllyWhenDismissed")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeJoinAllyWhenDismissed();
@Key("DaysBeforeAcceptNewClanWhenDismissed")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeAcceptNewClanWhenDismissed();
@Key("DaysBeforeCreateNewAllyWhenDissolved")
@ConverterClass(Days2MillisecondsConverter.class)
long getDaysBeforeCreateNewAllyWhenDissolved();
@Key("MaxNumOfClansInAlly")
int getMaxNumOfClansInAlly();
@Key("MembersCanWithdrawFromClanWH")
boolean membersCanWithdrawFromClanWH();
@Key("RemoveCastleCirclets")
boolean removeCastleCirclets();
@Key("ClanMembersForWar")
int getClanMembersForWar();
// Party
@Key("PartyRange")
int getPartyRange();
@Key("PartyRange2")
int getPartyRange2();
@Key("PartyEvenlyDistributeAllStackableItems")
boolean getPartyEvenlyDistributeAllStackableItems();
@Key("PartyEvenlyDistributeAllOtherItems")
boolean getPartyEvenlyDistributeAllOtherItems();
@Key("PartyEvenlyDistributeItems")
List<Integer> getPartyEvenlyDistributeItems();
@Key("LeavePartyLeader")
boolean leavePartyLeader();
// Initial
@Key("InitialEquipmentEvent")
boolean initialEquipmentEvent();
@Key("StartingAdena")
int getStartingAdena();
@Key("StartingLevel")
int getStartingLevel();
@Key("StartingSP")
int getStartingSP();
// Other
@Key("MaxAdena")
long getMaxAdena();
@Key("AutoLoot")
boolean autoLoot();
@Key("AutoLootRaids")
boolean autoLootRaids();
@Key("RaidLootRightsInterval")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getRaidLootRightsInterval();
@Key("RaidLootRightsCCSize")
int getRaidLootRightsCCSize();
@Key("UnstuckInterval")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getUnstuckInterval();
@Key("TeleportWatchdogTimeout")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getTeleportWatchdogTimeout();
@Key("PlayerSpawnProtection")
int getPlayerSpawnProtection();
@Key("PlayerSpawnProtectionAllowedItems")
Set<Integer> getPlayerSpawnProtectionAllowedItems();
@Key("PlayerTeleportProtection")
int getPlayerTeleportProtection();
@Key("RandomRespawnInTown")
boolean randomRespawnInTown();
@Key("OffsetOnTeleport")
boolean offsetOnTeleport();
@Key("MaxOffsetOnTeleport")
int getMaxOffsetOnTeleport();
@Key("PetitioningAllowed")
boolean petitioningAllowed();
@Key("MaxPetitionsPerPlayer")
int getMaxPetitionsPerPlayer();
@Key("MaxPetitionsPending")
int getMaxPetitionsPending();
@Key("FreeTeleporting")
boolean freeTeleporting();
@Key("DeleteCharAfterDays")
int getDeleteCharAfterDays();
@Key("ExponentXp")
int getExponentXp();
@Key("ExponentSp")
int getExponentSp();
@Key("PartyXpCutoffMethod")
String getPartyXpCutoffMethod();
@Key("PartyXpCutoffPercent")
double getPartyXpCutoffPercent();
@Key("PartyXpCutoffLevel")
int getPartyXpCutoffLevel();
@Key("PartyXpCutoffGaps")
@ConverterClass(MapIntegerIntegerConverter.class)
Map<Integer, Integer> getPartyXpCutoffGaps();
@Key("PartyXpCutoffGapPercent")
List<Integer> getPartyXpCutoffGapPercent();
@Key("Tutorial")
boolean tutorial();
@Key("ExpertisePenalty")
boolean expertisePenalty();
@Key("StoreUISettings")
boolean storeUISettings();
@Key("SilenceModeExclude")
boolean silenceModeExclude();
@Key("ValidateTriggerSkills")
boolean validateTriggerSkills();
@Key("PlayerNameTemplate")
@ConverterClass(PatternConverter.class)
Pattern getPlayerNameTemplate();
@Key("PetNameTemplate")
@ConverterClass(PatternConverter.class)
Pattern getPetNameTemplate();
@Key("ForbiddenNames")
Set<String> getForbiddenNames();
@Key("CharMaxNumber")
int getCharMaxNumber();
}
@@ -0,0 +1,175 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.regex.Pattern;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.PatternConverter;
/**
* Clan Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/clan.properties",
"file:./config/clan.properties",
"classpath:config/clan.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface ClanConfiguration extends Reloadable {
@Key("ClanNameTemplate")
@ConverterClass(PatternConverter.class)
Pattern getClanNameTemplate();
@Key("TakeFortPoints")
int getTakeFortPoints();
@Key("TakeCastlePoints")
int getTakeCastlePoints();
@Key("CastleDefendedPoints")
int getCastleDefendedPoints();
@Key("FestivalOfDarknessWin")
int getFestivalOfDarknessWin();
@Key("HeroPoints")
int getHeroPoints();
@Key("CompleteAcademyMinPoints")
int getCompleteAcademyMinPoints();
@Key("CompleteAcademyMaxPoints")
int getCompleteAcademyMaxPoints();
@Key("KillBallistaPoints")
int getKillBallistaPoints();
@Key("BloodAlliancePoints")
int getBloodAlliancePoints();
@Key("BloodOathPoints")
int getBloodOathPoints();
@Key("KnightsEpaulettePoints")
int getKnightsEpaulettePoints();
@Key("1stRaidRankingPoints")
int get1stRaidRankingPoints();
@Key("2ndRaidRankingPoints")
int get2ndRaidRankingPoints();
@Key("3rdRaidRankingPoints")
int get3rdRaidRankingPoints();
@Key("4thRaidRankingPoints")
int get4thRaidRankingPoints();
@Key("5thRaidRankingPoints")
int get5thRaidRankingPoints();
@Key("6thRaidRankingPoints")
int get6thRaidRankingPoints();
@Key("7thRaidRankingPoints")
int get7thRaidRankingPoints();
@Key("8thRaidRankingPoints")
int get8thRaidRankingPoints();
@Key("9thRaidRankingPoints")
int get9thRaidRankingPoints();
@Key("10thRaidRankingPoints")
int get10thRaidRankingPoints();
@Key("UpTo50thRaidRankingPoints")
int getUpTo50thRaidRankingPoints();
@Key("UpTo100thRaidRankingPoints")
int getUpTo100thRaidRankingPoints();
@Key("ReputationScorePerKill")
int getReputationScorePerKill();
@Key("LoseFortPoints")
int getLoseFortPoints();
@Key("LoseCastlePoints")
int getLoseCastlePoints();
@Key("CreateRoyalGuardCost")
int getCreateRoyalGuardCost();
@Key("CreateKnightUnitCost")
int getCreateKnightUnitCost();
// TODO(Zoey76): Implement ReinforceKnightUnitCost configuration.
@Key("ReinforceKnightUnitCost")
int getReinforceKnightUnitCost();
@Key("ClanLevel6Cost")
int getClanLevel6Cost();
@Key("ClanLevel7Cost")
int getClanLevel7Cost();
@Key("ClanLevel8Cost")
int getClanLevel8Cost();
@Key("ClanLevel9Cost")
int getClanLevel9Cost();
@Key("ClanLevel10Cost")
int getClanLevel10Cost();
@Key("ClanLevel11Cost")
int getClanLevel11Cost();
@Key("ClanLevel6Requirement")
int getClanLevel6Requirement();
@Key("ClanLevel7Requirement")
int getClanLevel7Requirement();
@Key("ClanLevel8Requirement")
int getClanLevel8Requirement();
@Key("ClanLevel9Requirement")
int getClanLevel9Requirement();
@Key("ClanLevel10Requirement")
int getClanLevel10Requirement();
@Key("ClanLevel11Requirement")
int getClanLevel11Requirement();
}
@@ -0,0 +1,239 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Clan Hall Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/clanhall.properties",
"file:./config/clanhall.properties",
"classpath:config/clanhall.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface ClanHallConfiguration extends Reloadable {
@Key("FunctionFeeDay1")
long getFunctionFeeDay1();
@Key("FunctionFeeDay2")
long getFunctionFeeDay2();
@Key("FunctionFeeDay3")
long getFunctionFeeDay3();
@Key("FunctionFeeDay7")
long getFunctionFeeDay7();
@Key("TeleportFunctionFeeLvl1")
int getTeleportFunctionFeeLvl1();
@Key("TeleportFunctionFeeLvl2")
int getTeleportFunctionFeeLvl2();
@Key("TeleportFunctionFeeLvl11")
int getTeleportFunctionFeeLvl11();
@Key("TeleportFunctionFeeLvl12")
int getTeleportFunctionFeeLvl12();
@Key("SupportFeeLvl1")
int getSupportFeeLvl1();
@Key("SupportFeeLvl2")
int getSupportFeeLvl2();
@Key("SupportFeeLvl3")
int getSupportFeeLvl3();
@Key("SupportFeeLvl4")
int getSupportFeeLvl4();
@Key("SupportFeeLvl5")
int getSupportFeeLvl5();
@Key("SupportFeeLvl7")
int getSupportFeeLvl7();
@Key("SupportFeeLvl8")
int getSupportFeeLvl8();
@Key("SupportFeeLvl15")
int getSupportFeeLvl15();
@Key("SupportFeeLvl18")
int getSupportFeeLvl18();
@Key("MpRegenerationFeeLvl1")
int getMpRegenerationFeeLvl1();
@Key("MpRegenerationFeeLvl3")
int getMpRegenerationFeeLvl3();
@Key("MpRegenerationFeeLvl5")
int getMpRegenerationFeeLvl5();
@Key("MpRegenerationFeeLvl6")
int getMpRegenerationFeeLvl6();
@Key("MpRegenerationFeeLvl8")
int getMpRegenerationFeeLvl8();
@Key("MpRegenerationFeeLvl18")
int getMpRegenerationFeeLvl18();
@Key("MpRegenerationFeeLvl20")
int getMpRegenerationFeeLvl20();
@Key("HpRegenerationFeeLvl2")
int getHpRegenerationFeeLvl2();
@Key("HpRegenerationFeeLvl4")
int getHpRegenerationFeeLvl4();
@Key("HpRegenerationFeeLvl5")
int getHpRegenerationFeeLvl5();
@Key("HpRegenerationFeeLvl6")
int getHpRegenerationFeeLvl6();
@Key("HpRegenerationFeeLvl7")
int getHpRegenerationFeeLvl7();
@Key("HpRegenerationFeeLvl8")
int getHpRegenerationFeeLvl8();
@Key("HpRegenerationFeeLvl9")
int getHpRegenerationFeeLvl9();
@Key("HpRegenerationFeeLvl10")
int getHpRegenerationFeeLvl10();
@Key("HpRegenerationFeeLvl12")
int getHpRegenerationFeeLvl12();
@Key("HpRegenerationFeeLvl13")
int getHpRegenerationFeeLvl13();
@Key("HpRegenerationFeeLvl15")
int getHpRegenerationFeeLvl15();
@Key("HpRegenerationFeeLvl25")
int getHpRegenerationFeeLvl25();
@Key("HpRegenerationFeeLvl30")
int getHpRegenerationFeeLvl30();
@Key("ExpRegenerationFeeLvl1")
int getExpRegenerationFeeLvl1();
@Key("ExpRegenerationFeeLvl3")
int getExpRegenerationFeeLvl3();
@Key("ExpRegenerationFeeLvl5")
int getExpRegenerationFeeLvl5();
@Key("ExpRegenerationFeeLvl6")
int getExpRegenerationFeeLvl6();
@Key("ExpRegenerationFeeLvl7")
int getExpRegenerationFeeLvl7();
@Key("ExpRegenerationFeeLvl8")
int getExpRegenerationFeeLvl8();
@Key("ExpRegenerationFeeLvl10")
int getExpRegenerationFeeLvl10();
@Key("ExpRegenerationFeeLvl19")
int getExpRegenerationFeeLvl19();
@Key("ExpRegenerationFeeLvl20")
int getExpRegenerationFeeLvl20();
@Key("ItemCreationFunctionFeeLvl1")
int getItemCreationFunctionFeeLvl1();
@Key("ItemCreationFunctionFeeLvl2")
int getItemCreationFunctionFeeLvl2();
@Key("ItemCreationFunctionFeeLvl3")
int getItemCreationFunctionFeeLvl3();
@Key("ItemCreationFunctionFeeLvl11")
int getItemCreationFunctionFeeLvl11();
@Key("ItemCreationFunctionFeeLvl12")
int getItemCreationFunctionFeeLvl12();
@Key("ItemCreationFunctionFeeLvl13")
int getItemCreationFunctionFeeLvl13();
@Key("CurtainFunctionFeeLvl1")
int getCurtainFunctionFeeLvl1();
@Key("CurtainFunctionFeeLvl2")
int getCurtainFunctionFeeLvl2();
@Key("FrontPlatformFunctionFeeLvl1")
int getFrontPlatformFunctionFeeLvl1();
@Key("FrontPlatformFunctionFeeLvl2")
int getFrontPlatformFunctionFeeLvl2();
@Key("BroadCastFunctionFeeLvl1")
int getBroadCastFunctionFeeLvl1();
@Key("BroadCastFunctionFeeLvl2")
int getBroadCastFunctionFeeLvl2();
@Key("MpBuffFree")
boolean mpBuffFree();
@Key("MinClanLevel")
int getMinClanLevel();
@Key("MaxAttackers")
int getMaxAttackers();
// TODO(Zoey76): Implement MaxFlagsPerClan configuration.
@Key("MaxFlagsPerClan")
int getMaxFlagsPerClan();
@Key("EnableFame")
boolean enableFame();
@Key("FameAmount")
int getFameAmount();
@Key("FameFrequency")
int getFameFrequency();
}
@@ -0,0 +1,249 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import java.nio.file.Path;
import org.aeonbits.owner.ConfigFactory;
/**
* Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
public class Configuration {
public static final String DEFAULT_PATH = "config/";
public static final String CUSTOM_SUBPATH = "custom/game/config/";
public static final String EOL = System.lineSeparator();
private static final IPConfigData ipConfigData = new IPConfigData();
private static final ServerConfiguration server = ConfigFactory.create(ServerConfiguration.class);
private static final HexIdConfiguration hexId = ConfigFactory.create(HexIdConfiguration.class);
private static final DatabaseConfiguration database = ConfigFactory.create(DatabaseConfiguration.class);
private static final MMOConfiguration mmo = ConfigFactory.create(MMOConfiguration.class);
private static final TelnetConfiguration telnet = ConfigFactory.create(TelnetConfiguration.class);
private static final GeneralConfiguration general = ConfigFactory.create(GeneralConfiguration.class);
private static final GeodataConfiguration geodata = ConfigFactory.create(GeodataConfiguration.class);
private static final CharacterConfiguration character = ConfigFactory.create(CharacterConfiguration.class);
private static final CastleConfiguration castle = ConfigFactory.create(CastleConfiguration.class);
private static final ClanConfiguration clan = ConfigFactory.create(ClanConfiguration.class);
private static final ClanHallConfiguration clanhall = ConfigFactory.create(ClanHallConfiguration.class);
private static final FortressConfiguration fortress = ConfigFactory.create(FortressConfiguration.class);
private static final SevenSignsConfiguration sevenSigns = ConfigFactory.create(SevenSignsConfiguration.class);
private static final TvTConfiguration tvt = ConfigFactory.create(TvTConfiguration.class);
private static final NPCConfiguration npc = ConfigFactory.create(NPCConfiguration.class);
private static final OlympiadConfiguration olympiad = ConfigFactory.create(OlympiadConfiguration.class);
private static final SiegeConfiguration siege = ConfigFactory.create(SiegeConfiguration.class);
private static final TerritoryWarConfiguration territoryWar = ConfigFactory.create(TerritoryWarConfiguration.class);
private static final FortSiegeConfiguration fortSiege = ConfigFactory.create(FortSiegeConfiguration.class);
private static final GrandBossConfiguration grandBoss = ConfigFactory.create(GrandBossConfiguration.class);
private static final GraciaSeedsConfiguration graciaSeeds = ConfigFactory.create(GraciaSeedsConfiguration.class);
private static final RatesConfiguration rates = ConfigFactory.create(RatesConfiguration.class);
private static final VitalityConfiguration vitality = ConfigFactory.create(VitalityConfiguration.class);
private static final PvPConfiguration pvp = ConfigFactory.create(PvPConfiguration.class);
private static final CustomsConfiguration customs = ConfigFactory.create(CustomsConfiguration.class);
private static final HuntingSystemConfiguration hunting = ConfigFactory.create(HuntingSystemConfiguration.class);
private static final FloodProtectorConfiguration floodProtector = ConfigFactory.create(FloodProtectorConfiguration.class);
private static final BufferServiceConfiguration bufferService = ConfigFactory.create(BufferServiceConfiguration.class);
private static final TeleporterServiceConfiguration teleporterService = ConfigFactory.create(TeleporterServiceConfiguration.class);
private static final DiscordConfiguration discord = ConfigFactory.create(DiscordConfiguration.class);
private Configuration() {
// Do nothing.
}
public static ServerConfiguration server() {
return server;
}
public static HexIdConfiguration hexId() {
return hexId;
}
public static DatabaseConfiguration database() {
return database;
}
public static MMOConfiguration mmo() {
return mmo;
}
public static TelnetConfiguration telnet() {
return telnet;
}
public static GeneralConfiguration general() {
return general;
}
public static GeodataConfiguration geodata() {
return geodata;
}
public static CharacterConfiguration character() {
return character;
}
public static CastleConfiguration castle() {
return castle;
}
public static ClanConfiguration clan() {
return clan;
}
public static ClanHallConfiguration clanhall() {
return clanhall;
}
public static FortressConfiguration fortress() {
return fortress;
}
public static SevenSignsConfiguration sevenSigns() {
return sevenSigns;
}
public static TvTConfiguration tvt() {
return tvt;
}
public static NPCConfiguration npc() {
return npc;
}
public static OlympiadConfiguration olympiad() {
return olympiad;
}
public static SiegeConfiguration siege() {
return siege;
}
public static TerritoryWarConfiguration territoryWar() {
return territoryWar;
}
public static FortSiegeConfiguration fortSiege() {
return fortSiege;
}
public static GrandBossConfiguration grandBoss() {
return grandBoss;
}
public static GraciaSeedsConfiguration graciaSeeds() {
return graciaSeeds;
}
public static RatesConfiguration rates() {
return rates;
}
public static VitalityConfiguration vitality() {
return vitality;
}
public static PvPConfiguration pvp() {
return pvp;
}
public static CustomsConfiguration customs() {
return customs;
}
public static HuntingSystemConfiguration hunting() {
return hunting;
}
public static FloodProtectorConfiguration floodProtector() {
return floodProtector;
}
public static IPConfigData ip() {
return ipConfigData;
}
public static BufferServiceConfiguration bufferService() {
return bufferService;
}
public static TeleporterServiceConfiguration teleporterService() {
return teleporterService;
}
public static DiscordConfiguration discord() {
return discord;
}
public static String getDefaultPath(String filename) {
return DEFAULT_PATH + filename;
}
public static String getCustomSubpath(String filename) {
return CUSTOM_SUBPATH + filename;
}
/**
* Returns either the custom or default path of the config file.<br>
* When the L2J_HOME variable is defined, the custom path to the config file is returned. If not, the default path to the config file is returned.
* @param filename the filename
* @return either the custom or default path to the config file
*/
public static Path getCustomOrDefaultPath(String filename) {
var l2jHome = server().getL2jHome();
if (l2jHome == null) {
return Path.of(getDefaultPath(filename));
}
return Path.of(l2jHome, getCustomSubpath(filename));
}
}
@@ -0,0 +1,310 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.Map;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.ColorConverter;
import com.l2jserver.gameserver.config.converter.IPLimitConverter;
import com.l2jserver.gameserver.config.converter.Seconds2MillisecondsConverter;
/**
* Customs Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/customs.properties",
"file:./config/customs.properties",
"classpath:config/customs.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface CustomsConfiguration extends Reloadable {
@Key("ChampionEnable")
boolean championEnable();
@Key("ChampionPassive")
boolean championPassive();
@Key("ChampionFrequency")
int getChampionFrequency();
@Key("ChampionTitle")
String getChampionTitle();
@Key("ChampionMinLevel")
int getChampionMinLevel();
@Key("ChampionMaxLevel")
int getChampionMaxLevel();
@Key("ChampionHp")
int getChampionHp();
@Key("ChampionHpRegen")
double getChampionHpRegen();
@Key("ChampionRewardsExpSp")
double getChampionRewardsExpSp();
@Key("ChampionRewardsChance")
double getChampionRewardsChance();
@Key("ChampionRewardsAmount")
double getChampionRewardsAmount();
@Key("ChampionAdenasRewardsChance")
double getChampionAdenasRewardsChance();
@Key("ChampionAdenasRewardsAmount")
double getChampionAdenasRewardsAmount();
@Key("ChampionAtk")
float getChampionAtk();
@Key("ChampionSpdAtk")
float getChampionSpdAtk();
@Key("ChampionRewardItemID")
int getChampionRewardItemID();
@Key("ChampionRewardItemQty")
int getChampionRewardItemQty();
@Key("ChampionRewardLowerLvlItemChance")
int getChampionRewardLowerLvlItemChance();
@Key("ChampionRewardHigherLvlItemChance")
int getChampionRewardHigherLvlItemChance();
@Key("ChampionEnableVitality")
boolean championEnableVitality();
@Key("ChampionEnableInInstances")
boolean championEnableInInstances();
@Key("AllowWedding")
boolean allowWedding();
@Key("WeddingPrice")
int getWeddingPrice();
@Key("WeddingPunishInfidelity")
boolean weddingPunishInfidelity();
// TODO(Zoey76): Implement WeddingTeleport configuration.
@Key("WeddingTeleport")
boolean weddingTeleport();
@Key("WeddingTeleportPrice")
int getWeddingTeleportPrice();
@Key("WeddingTeleportDuration")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getWeddingTeleportDuration();
@Key("WeddingAllowSameSex")
boolean weddingAllowSameSex();
@Key("WeddingFormalWear")
boolean weddingFormalWear();
@Key("WeddingDivorceCosts")
int getWeddingDivorceCosts();
@Key("BankingEnabled")
boolean bankingEnabled();
@Key("BankingGoldbarCount")
int getBankingGoldbarCount();
@Key("BankingAdenaCount")
int getBankingAdenaCount();
@Key("EnableWarehouseSortingClan")
boolean enableWarehouseSortingClan();
@Key("EnableWarehouseSortingPrivate")
boolean enableWarehouseSortingPrivate();
@Key("OfflineTradeEnable")
boolean offlineTradeEnable();
@Key("OfflineCraftEnable")
boolean offlineCraftEnable();
@Key("OfflineModeInPeaceZone")
boolean offlineModeInPeaceZone();
@Key("OfflineModeNoDamage")
boolean offlineModeNoDamage();
@Key("OfflineSetNameColor")
boolean offlineSetNameColor();
@Key("OfflineNameColor")
@ConverterClass(ColorConverter.class)
int getOfflineNameColor();
@Key("OfflineFame")
boolean offlineFame();
@Key("RestoreOffliners")
boolean restoreOffliners();
@Key("OfflineMaxDays")
int getOfflineMaxDays();
@Key("OfflineDisconnectFinished")
boolean offlineDisconnectFinished();
@Key("EnableManaPotionSupport")
boolean enableManaPotionSupport();
@Key("DisplayServerTime")
boolean displayServerTime();
@Key("ScreenWelcomeMessageEnable")
boolean screenWelcomeMessageEnable();
@Key("ScreenWelcomeMessageText")
String getScreenWelcomeMessageText();
@Key("ScreenWelcomeMessageTime")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getScreenWelcomeMessageTime();
@Key("AntiFeedEnable")
boolean antiFeedEnable();
@Key("AntiFeedDualbox")
boolean antiFeedDualbox();
@Key("AntiFeedDisconnectedAsDualbox")
boolean antiFeedDisconnectedAsDualbox();
@Key("AntiFeedInterval")
int getAntiFeedInterval();
@Key("AnnouncePkPvP")
boolean announcePkPvP();
@Key("AnnouncePkPvPNormalMessage")
boolean announcePkPvPNormalMessage();
@Key("AnnouncePkMsg")
String getAnnouncePkMsg();
@Key("AnnouncePvpMsg")
String getAnnouncePvpMsg();
@Key("ChatAdmin")
boolean chatAdmin();
@Key("HellboundStatus")
boolean hellboundStatus();
@Key("AutoLootVoiceEnable")
boolean autoLootVoiceCommand();
@Key("AutoLootVoiceRestore")
boolean autoLootVoiceRestore();
@Key("AutoLootItemsVoiceRestore")
boolean autoLootItemsVoiceRestore();
@Key("AutoLootHerbsVoiceRestore")
boolean autoLootHerbsVoiceRestore();
@Key("AutoLootHerbsList")
Set<Integer> getAutoLootHerbsList();
@Key("AutoLootItemsList")
Set<Integer> getAutoLootItemsList();
@Key("MultiLangEnable")
boolean multiLangEnable();
@Key("MultiLangDefault")
String getMultiLangDefault();
@Key("MultiLangAllowed")
Set<String> getMultiLangAllowed();
@Key("MultiLangHandler")
boolean multiLangHandler();
@Key("MultiLangSystemMessageEnable")
boolean multiLangSystemMessageEnable();
@Key("MultiLangSystemMessageAllowed")
Set<String> getMultiLangSystemMessageAllowed();
@Key("MultiLangNpcStringEnable")
boolean multiLangNpcStringEnable();
@Key("MultiLangNpcStringAllowed")
Set<String> getMultiLangNpcStringAllowed();
@Key("L2WalkerProtection")
boolean l2WalkerProtection();
@Key("DebugHandler")
boolean debugHandler();
@Key("DualboxCheckMaxPlayersPerIP")
int getDualboxCheckMaxPlayersPerIP();
@Key("DualboxCheckMaxOlympiadParticipantsPerIP")
int getDualboxCheckMaxOlympiadParticipantsPerIP();
@Key("DualboxCheckMaxL2EventParticipantsPerIP")
int getDualboxCheckMaxL2EventParticipantsPerIP();
@Key("DualboxCheckWhitelist")
@ConverterClass(IPLimitConverter.class)
Map<Integer, Integer> getDualboxCheckWhitelist();
@Key("AllowChangePassword")
boolean allowChangePassword();
@Key("AllowXpHandler")
boolean allowXpHandler();
@Key("AllowCastleHandler")
boolean allowCastleHandler();
@Key("AllowClanHandler")
boolean allowClanHandler();
@Key("AllowStatHandler")
boolean allowStatHandler();
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Database Configuration.
* @author Zoey76
* @version 2.6.3.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/database.properties",
"file:./config/database.properties",
"classpath:config/database.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface DatabaseConfiguration extends Reloadable {
@Key("URL")
String getURL();
@Key("User")
String getUser();
@Key("Password")
String getPassword();
@Key("MaxConnections")
int getMaxConnections();
@Key("MaxIdleTime")
int getMaxIdleTime();
}
@@ -0,0 +1,65 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
/**
* Discord Configuration.
* @author Stalitsa
* @version 2.6.2.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/discord.properties",
"file:./config/discord.properties",
"classpath:config/discord.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface DiscordConfiguration extends Mutable, Reloadable {
@Key("BotEnable")
boolean enableBot();
@Key("BotPrefix")
String getPrefix();
@Key("BotToken")
String getBotToken();
@Key("ServerId")
String getServerId();
@Key("GameMasterId")
String getGameMasterId();
@Key("ConsoleLogChannelId")
String getConsoleLogChannelId();
@Key("GameChatChannelId")
String getGameChatChannelId();
}
@@ -0,0 +1,286 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Accessible;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Flood Protector Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/floodprotector.properties",
"file:./config/floodprotector.properties",
"classpath:config/floodprotector.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface FloodProtectorConfiguration extends Accessible, Reloadable {
// TODO(Zoey76): Implement all these configurations.
@Key("UseItemInterval")
int getUseItemInterval();
@Key("UseItemLogFlooding")
boolean useItemLogFlooding();
@Key("UseItemPunishmentLimit")
int getUseItemPunishmentLimit();
@Key("UseItemPunishmentType")
String getUseItemPunishmentType();
@Key("UseItemPunishmentTime")
int getUseItemPunishmentTime();
@Key("RollDiceInterval")
int getRollDiceInterval();
@Key("RollDiceLogFlooding")
boolean rollDiceLogFlooding();
@Key("RollDicePunishmentLimit")
int getRollDicePunishmentLimit();
@Key("RollDicePunishmentType")
String getRollDicePunishmentType();
@Key("RollDicePunishmentTime")
int getRollDicePunishmentTime();
@Key("FireworkInterval")
int getFireworkInterval();
@Key("FireworkLogFlooding")
boolean fireworkLogFlooding();
@Key("FireworkPunishmentLimit")
int getFireworkPunishmentLimit();
@Key("FireworkPunishmentType")
String getFireworkPunishmentType();
@Key("FireworkPunishmentTime")
int getFireworkPunishmentTime();
@Key("ItemPetSummonInterval")
int geItemPetSummonInterval();
@Key("ItemPetSummonLogFlooding")
boolean itemPetSummonLogFlooding();
@Key("ItemPetSummonPunishmentLimit")
int getItemPetSummonPunishmentLimit();
@Key("ItemPetSummonPunishmentType")
String getItemPetSummonPunishmentType();
@Key("ItemPetSummonPunishmentTime")
int getItemPetSummonPunishmentTime();
@Key("HeroVoiceInterval")
int getHeroVoiceInterval();
@Key("HeroVoiceLogFlooding")
boolean heroVoiceLogFlooding();
@Key("HeroVoicePunishmentLimit")
int getHeroVoicePunishmentLimit();
@Key("HeroVoicePunishmentType")
String getHeroVoicePunishmentType();
@Key("HeroVoicePunishmentTime")
int getHeroVoicePunishmentTime();
@Key("GlobalChatInterval")
int getGlobalChatInterval();
@Key("GlobalChatLogFlooding")
boolean globalChatLogFlooding();
@Key("GlobalChatPunishmentLimit")
int getGlobalChatPunishmentLimit();
@Key("GlobalChatPunishmentType")
String getGlobalChatPunishmentType();
@Key("GlobalChatPunishmentTime")
int getGlobalChatPunishmentTime();
@Key("SubclassInterval")
int getSubclassInterval();
@Key("SubclassLogFlooding")
boolean subclassLogFlooding();
@Key("SubclassPunishmentLimit")
int getSubclassPunishmentLimit();
@Key("SubclassPunishmentType")
String getSubclassPunishmentType();
@Key("SubclassPunishmentTime")
int getSubclassPunishmentTime();
@Key("DropItemInterval")
int getDropItemInterval();
@Key("DropItemLogFlooding")
boolean dropItemLogFlooding();
@Key("DropItemPunishmentLimit")
int getDropItemPunishmentLimit();
@Key("DropItemPunishmentType")
String getDropItemPunishmentType();
@Key("DropItemPunishmentTime")
int getDropItemPunishmentTime();
@Key("ServerBypassInterval")
int getServerBypassInterval();
@Key("ServerBypassLogFlooding")
boolean serverBypassLogFlooding();
@Key("ServerBypassPunishmentLimit")
int getServerBypassPunishmentLimit();
@Key("ServerBypassPunishmentType")
String getServerBypassPunishmentType();
@Key("ServerBypassPunishmentTime")
int getServerBypassPunishmentTime();
@Key("MultiSellInterval")
int getMultiSellInterval();
@Key("MultiSellLogFlooding")
boolean multiSellLogFlooding();
@Key("MultiSellPunishmentLimit")
int getMultiSellPunishmentLimit();
@Key("MultiSellPunishmentType")
String getMultiSellPunishmentType();
@Key("MultiSellPunishmentTime")
int getMultiSellPunishmentTime();
@Key("TransactionInterval")
int getTransactionInterval();
@Key("TransactionLogFlooding")
boolean transactionLogFlooding();
@Key("TransactionPunishmentLimit")
int getTransactionPunishmentLimit();
@Key("TransactionPunishmentType")
String getTransactionPunishmentType();
@Key("TransactionPunishmentTime")
int getTransactionPunishmentTime();
@Key("ManufactureInterval")
int getManufactureInterval();
@Key("ManufactureLogFlooding")
boolean manufactureLogFlooding();
@Key("ManufacturePunishmentLimit")
int getManufacturePunishmentLimit();
@Key("ManufacturePunishmentType")
String getManufacturePunishmentType();
@Key("ManufacturePunishmentTime")
int getManufacturePunishmentTime();
@Key("ManorInterval")
int getManorInterval();
@Key("ManorLogFlooding")
boolean manorLogFlooding();
@Key("ManorPunishmentLimit")
int getManorPunishmentLimit();
@Key("ManorPunishmentType")
String getManorPunishmentType();
@Key("ManorPunishmentTime")
int getManorPunishmentTime();
@Key("SendMailInterval")
int getSendMailInterval();
@Key("SendMailLogFlooding")
boolean sendMailLogFlooding();
@Key("SendMailPunishmentLimit")
int getSendMailPunishmentLimit();
@Key("SendMailPunishmentType")
String getSendMailPunishmentType();
@Key("SendMailPunishmentTime")
int getSendMailPunishmentTime();
@Key("CharacterSelectInterval")
int getCharacterSelectInterval();
@Key("CharacterSelectLogFlooding")
boolean characterSelectLogFlooding();
@Key("CharacterSelectPunishmentLimit")
int getCharacterSelectPunishmentLimit();
@Key("CharacterSelectPunishmentType")
String getCharacterSelectPunishmentType();
@Key("CharacterSelectPunishmentTime")
int getCharacterSelectPunishmentTime();
@Key("ItemAuctionInterval")
int getItemAuctionInterval();
@Key("ItemAuctionLogFlooding")
boolean itemAuctionLogFlooding();
@Key("ItemAuctionPunishmentLimit")
int getItemAuctionPunishmentLimit();
@Key("ItemAuctionPunishmentType")
String getItemAuctionPunishmentType();
@Key("ItemAuctionPunishmentTime")
int getItemAuctionPunishmentTime();
}
@@ -0,0 +1,478 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Accessible;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Minutes2MillisecondsConverter;
/**
* Fort Siege Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/fortsiege.properties",
"file:./config/fortsiege.properties",
"classpath:config/fortsiege.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface FortSiegeConfiguration extends Accessible, Reloadable {
@Key("SiegeLength")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getSiegeLength();
@Key("SuspiciousMerchantRespawnDelay")
int getSuspiciousMerchantRespawnDelay();
@Key("CountDownLength")
int getCountDownLength();
@Key("MaxFlags")
int getMaxFlags();
@Key("SiegeClanMinLevel")
int getSiegeClanMinLevel();
@Key("AttackerMaxClans")
int getAttackerMaxClans();
@Key("JustToTerritory")
boolean justToTerritory();
// TODO(Zoey76): Most, if not all, of this configurations are not used.
@Key("ShantyCommander1")
String getShantyCommander1();
@Key("ShantyCommander2")
String getShantyCommander2();
@Key("ShantyCommander3")
String getShantyCommander3();
@Key("ShantyFlag1")
String getShantyFlag1();
@Key("ShantyFlag2")
String getShantyFlag2();
@Key("ShantyFlag3")
String getShantyFlag3();
@Key("SouthernCommander1")
String getSouthernCommander1();
@Key("SouthernCommander2")
String getSouthernCommander2();
@Key("SouthernCommander3")
String getSouthernCommander3();
@Key("SouthernCommander4")
String getSouthernCommander4();
@Key("SouthernFlag1")
String getSouthernFlag1();
@Key("SouthernFlag2")
String getSouthernFlag2();
@Key("SouthernFlag3")
String getSouthernFlag3();
@Key("HiveCommander1")
String getHiveCommander1();
@Key("HiveCommander2")
String getHiveCommander2();
@Key("HiveCommander3")
String getHiveCommander3();
@Key("HiveFlag1")
String getHiveFlag1();
@Key("HiveFlag2")
String getHiveFlag2();
@Key("HiveFlag3")
String getHiveFlag3();
@Key("ValleyCommander1")
String getValleyCommander1();
@Key("ValleyCommander2")
String getValleyCommander2();
@Key("ValleyCommander3")
String getValleyCommander3();
@Key("ValleyCommander4")
String getValleyCommander4();
@Key("ValleyFlag1")
String getValleyFlag1();
@Key("ValleyFlag2")
String getValleyFlag2();
@Key("ValleyFlag3")
String getValleyFlag3();
@Key("IvoryCommander1")
String getIvoryCommander1();
@Key("IvoryCommander2")
String getIvoryCommander2();
@Key("IvoryCommander3")
String getIvoryCommander3();
@Key("IvoryFlag1")
String getIvoryFlag1();
@Key("IvoryFlag2")
String getIvoryFlag2();
@Key("IvoryFlag3")
String getIvoryFlag3();
@Key("NarsellCommander1")
String getNarsellCommander1();
@Key("NarsellCommander2")
String getNarsellCommander2();
@Key("NarsellCommander3")
String getNarsellCommander3();
@Key("NarsellFlag1")
String getNarsellFlag1();
@Key("NarsellFlag2")
String getNarsellFlag2();
@Key("NarsellFlag3")
String getNarsellFlag3();
@Key("BayouCommander1")
String getBayouCommander1();
@Key("BayouCommander2")
String getBayouCommander2();
@Key("BayouCommander3")
String getBayouCommander3();
@Key("BayouCommander4")
String getBayouCommander4();
@Key("BayouFlag1")
String getBayouFlag1();
@Key("BayouFlag2")
String getBayouFlag2();
@Key("BayouFlag3")
String getBayouFlag3();
@Key("WhiteSandsCommander1")
String getWhiteSandsCommander1();
@Key("WhiteSandsCommander2")
String getWhiteSandsCommander2();
@Key("WhiteSandsCommander3")
String getWhiteSandsCommander3();
@Key("WhiteSandsFlag1")
String getWhiteSandsFlag1();
@Key("WhiteSandsFlag2")
String getWhiteSandsFlag2();
@Key("WhiteSandsFlag3")
String getWhiteSandsFlag3();
@Key("BorderlandCommander1")
String getBorderlandCommander1();
@Key("BorderlandCommander2")
String getBorderlandCommander2();
@Key("BorderlandCommander3")
String getBorderlandCommander3();
@Key("BorderlandCommander4")
String getBorderlandCommander4();
@Key("BorderlandFlag1")
String getBorderlandFlag1();
@Key("BorderlandFlag2")
String getBorderlandFlag2();
@Key("BorderlandFlag3")
String getBorderlandFlag3();
@Key("SwampCommander1")
String getSwampCommander1();
@Key("SwampCommander2")
String getSwampCommander2();
@Key("SwampCommander3")
String getSwampCommander3();
@Key("SwampCommander4")
String getSwampCommander4();
@Key("SwampFlag1")
String getSwampFlag1();
@Key("SwampFlag2")
String getSwampFlag2();
@Key("SwampFlag3")
String getSwampFlag3();
@Key("ArchaicCommander1")
String getArchaicCommander1();
@Key("ArchaicCommander2")
String getArchaicCommander2();
@Key("ArchaicCommander3")
String getArchaicCommander3();
@Key("ArchaicFlag1")
String getArchaicFlag1();
@Key("ArchaicFlag2")
String getArchaicFlag2();
@Key("ArchaicFlag3")
String getArchaicFlag3();
@Key("FloranCommander1")
String getFloranCommander1();
@Key("FloranCommander2")
String getFloranCommander2();
@Key("FloranCommander3")
String getFloranCommander3();
@Key("FloranCommander4")
String getFloranCommander4();
@Key("FloranFlag1")
String getFloranFlag1();
@Key("FloranFlag2")
String getFloranFlag2();
@Key("FloranFlag3")
String getFloranFlag3();
@Key("CloudMountainCommander1")
String getCloudMountainCommander1();
@Key("CloudMountainCommander2")
String getCloudMountainCommander2();
@Key("CloudMountainCommander3")
String getCloudMountainCommander3();
@Key("CloudMountainCommander4")
String getCloudMountainCommander4();
@Key("CloudMountainFlag1")
String getCloudMountainFlag1();
@Key("CloudMountainFlag2")
String getCloudMountainFlag2();
@Key("CloudMountainFlag3")
String getCloudMountainFlag3();
@Key("TanorCommander1")
String getTanorCommander1();
@Key("TanorCommander2")
String getTanorCommander2();
@Key("TanorCommander3")
String getTanorCommander3();
@Key("TanorFlag1")
String getTanorFlag1();
@Key("TanorFlag2")
String getTanorFlag2();
@Key("TanorFlag3")
String getTanorFlag3();
@Key("DragonspineCommander1")
String getDragonspineCommander1();
@Key("DragonspineCommander2")
String getDragonspineCommander2();
@Key("DragonspineCommander3")
String getDragonspineCommander3();
@Key("DragonspineFlag1")
String getDragonspineFlag1();
@Key("DragonspineFlag2")
String getDragonspineFlag2();
@Key("DragonspineFlag3")
String getDragonspineFlag3();
@Key("AntharasCommander1")
String getAntharasCommander1();
@Key("AntharasCommander2")
String getAntharasCommander2();
@Key("AntharasCommander3")
String getAntharasCommander3();
@Key("AntharasCommander4")
String getAntharasCommander4();
@Key("AntharasFlag1")
String getAntharasFlag1();
@Key("AntharasFlag2")
String getAntharasFlag2();
@Key("AntharasFlag3")
String getAntharasFlag3();
@Key("WesternCommander1")
String getWesternCommander1();
@Key("WesternCommander2")
String getWesternCommander2();
@Key("WesternCommander3")
String getWesternCommander3();
@Key("WesternCommander4")
String getWesternCommander4();
@Key("WesternFlag1")
String getWesternFlag1();
@Key("WesternFlag2")
String getWesternFlag2();
@Key("WesternFlag3")
String getWesternFlag3();
@Key("HuntersCommander1")
String getHuntersCommander1();
@Key("HuntersCommander2")
String getHuntersCommander2();
@Key("HuntersCommander3")
String getHuntersCommander3();
@Key("HuntersCommander4")
String getHuntersCommander4();
@Key("HuntersFlag1")
String getHuntersFlag1();
@Key("HuntersFlag2")
String getHuntersFlag2();
@Key("HuntersFlag3")
String getHuntersFlag3();
@Key("AaruCommander1")
String getAaruCommander1();
@Key("AaruCommander2")
String getAaruCommander2();
@Key("AaruCommander3")
String getAaruCommander3();
@Key("AaruFlag1")
String getAaruFlag1();
@Key("AaruFlag2")
String getAaruFlag2();
@Key("AaruFlag3")
String getAaruFlag3();
@Key("DemonCommander1")
String getDemonCommander1();
@Key("DemonCommander2")
String getDemonCommander2();
@Key("DemonCommander3")
String getDemonCommander3();
@Key("DemonFlag1")
String getDemonFlag1();
@Key("DemonFlag2")
String getDemonFlag2();
@Key("DemonFlag3")
String getDemonFlag3();
@Key("MonasticCommander1")
String getMonasticCommander1();
@Key("MonasticCommander2")
String getMonasticCommander2();
@Key("MonasticCommander3")
String getMonasticCommander3();
@Key("MonasticFlag1")
String getMonasticFlag1();
@Key("MonasticFlag2")
String getMonasticFlag2();
@Key("MonasticFlag3")
String getMonasticFlag3();
}
@@ -0,0 +1,97 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Minutes2MillisecondsConverter;
/**
* Fortress Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/fortress.properties",
"file:./config/fortress.properties",
"classpath:config/fortress.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface FortressConfiguration extends Reloadable {
@Key("FunctionFeeDay7")
long getFunctionFeeDay7();
@Key("TeleportFunctionFeeLvl11")
int getTeleportFunctionFeeLvl11();
@Key("TeleportFunctionFeeLvl12")
int getTeleportFunctionFeeLvl12();
@Key("SupportFeeLvl15")
int getSupportFeeLvl15();
@Key("SupportFeeLvl18")
int getSupportFeeLvl18();
@Key("MpRegenerationFeeLvl18")
int getMpRegenerationFeeLvl18();
@Key("MpRegenerationFeeLvl20")
int getMpRegenerationFeeLvl20();
@Key("HpRegenerationFeeLvl25")
int getHpRegenerationFeeLvl25();
@Key("HpRegenerationFeeLvl30")
int getHpRegenerationFeeLvl30();
@Key("ExpRegenerationFeeLvl19")
int getExpRegenerationFeeLvl19();
@Key("ExpRegenerationFeeLvl20")
int getExpRegenerationFeeLvl20();
@Key("PeriodicUpdateFrequency")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getPeriodicUpdateFrequency();
@Key("BloodOathCount")
int getBloodOathCount();
@Key("MaxSupplyLevel")
int getMaxSupplyLevel();
@Key("FeeForCastle")
int getFeeForCastle();
@Key("MaxKeepTime")
int getMaxKeepTime();
@Key("MpBuffFree")
boolean mpBuffFree();
}
@@ -0,0 +1,650 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Minutes2MillisecondsConverter;
import com.l2jserver.gameserver.config.converter.Seconds2MillisecondsConverter;
import com.l2jserver.gameserver.config.converter.ServerListTypeConverter;
import com.l2jserver.gameserver.enums.IllegalActionPunishmentType;
/**
* General Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/general.properties",
"file:./config/general.properties",
"classpath:config/general.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface GeneralConfiguration extends Mutable, Reloadable {
@Key("EverybodyHasAdminRights")
boolean everybodyHasAdminRights();
@Key("ServerListBrackets")
boolean getServerListBrackets();
@Key("ServerListType")
@ConverterClass(ServerListTypeConverter.class)
int getServerListType();
@Key("ServerListAge")
int getServerListAge();
@Key("ServerGMOnly")
boolean serverGMOnly();
@Key("GMHeroAura")
boolean gmHeroAura();
@Key("GMStartupInvulnerable")
boolean gmStartupInvulnerable();
@Key("GMStartupInvisible")
boolean gmStartupInvisible();
@Key("GMStartupSilence")
boolean gmStartupSilence();
@Key("GMStartupAutoList")
boolean gmStartupAutoList();
@Key("GMStartupDietMode")
boolean gmStartupDietMode();
@Key("GMItemRestriction")
boolean gmItemRestriction();
@Key("GMSkillRestriction")
boolean gmSkillRestriction();
@Key("GMTradeRestrictedItems")
boolean gmTradeRestrictedItems();
@Key("GMRestartFighting")
boolean gmRestartFighting();
@Key("GMShowAnnouncerName")
boolean gmShowAnnouncerName();
// TODO(Zoey76): Implement GMShowCritAnnouncerName configuration.
@Key("GMShowCritAnnouncerName")
boolean gmShowCritAnnouncerName();
@Key("GMGiveSpecialSkills")
boolean gmGiveSpecialSkills();
@Key("GMGiveSpecialAuraSkills")
boolean gmGiveSpecialAuraSkills();
@Key("GameGuardEnforce")
boolean gameGuardEnforce();
// TODO(Zoey76): Implement GameGuardProhibitAction configuration.
@Key("GameGuardProhibitAction")
boolean gameGuardProhibitAction();
@Key("LogChat")
boolean logChat();
// TODO(Zoey76): Implement LogAutoAnnouncements configuration.
@Key("LogAutoAnnouncements")
boolean logAutoAnnouncements();
@Key("LogItems")
boolean logItems();
@Key("LogItemsSmallLog")
boolean logItemsSmallLog();
@Key("LogItemEnchants")
boolean logItemEnchants();
@Key("LogSkillEnchants")
boolean logSkillEnchants();
@Key("GMAudit")
boolean gmAudit();
@Key("SkillCheckEnable")
boolean skillCheckEnable();
@Key("SkillCheckRemove")
boolean skillCheckRemove();
@Key("SkillCheckGM")
boolean skillCheckGM();
@Key("ThreadPoolSizeEffects")
int getThreadPoolSizeEffects();
@Key("ThreadPoolSizeGeneral")
int getThreadPoolSizeGeneral();
@Key("ThreadPoolSizeEvents")
int getThreadPoolSizeEvents();
@Key("UrgentPacketThreadCoreSize")
int getUrgentPacketThreadCoreSize();
@Key("GeneralPacketThreadCoreSize")
int getGeneralPacketThreadCoreSize();
@Key("GeneralThreadCoreSize")
int getGeneralThreadCoreSize();
@Key("AiMaxThread")
int getAiMaxThread();
@Key("EventsMaxThread")
int getEventsMaxThread();
@Key("DeadLockDetector")
boolean deadLockDetector();
@Key("DeadLockCheckInterval")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getDeadLockCheckInterval();
@Key("RestartOnDeadlock")
boolean restartOnDeadlock();
@Key("ClientPacketQueueSize")
int getClientPacketQueueSize();
@Key("ClientPacketQueueMaxBurstSize")
int getClientPacketQueueMaxBurstSize();
@Key("ClientPacketQueueMaxPacketsPerSecond")
int getClientPacketQueueMaxPacketsPerSecond();
@Key("ClientPacketQueueMeasureInterval")
int getClientPacketQueueMeasureInterval();
@Key("ClientPacketQueueMaxAveragePacketsPerSecond")
int getClientPacketQueueMaxAveragePacketsPerSecond();
@Key("ClientPacketQueueMaxFloodsPerMin")
int getClientPacketQueueMaxFloodsPerMin();
@Key("ClientPacketQueueMaxOverflowsPerMin")
int getClientPacketQueueMaxOverflowsPerMin();
@Key("ClientPacketQueueMaxUnderflowsPerMin")
int getClientPacketQueueMaxUnderflowsPerMin();
@Key("ClientPacketQueueMaxUnknownPerMin")
int getClientPacketQueueMaxUnknownPerMin();
@Key("AllowDiscardItem")
boolean allowDiscardItem();
@Key("AutoDestroyDroppedItemAfter")
int getAutoDestroyDroppedItemAfter();
@Key("AutoDestroyHerbTime")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getAutoDestroyHerbTime();
@Key("ProtectedItems")
Set<Integer> getProtectedItems();
@Key("DatabaseCleanUp")
boolean databaseCleanUp();
// TODO(Zoey76): Implement ConnectionCloseTime configuration.
@Key("ConnectionCloseTime")
long getConnectionCloseTime();
@Key("CharacterDataStoreInterval")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getCharacterDataStoreInterval();
@Key("LazyItemsUpdate")
boolean lazyItemsUpdate();
@Key("UpdateItemsOnCharStore")
boolean updateItemsOnCharStore();
@Key("DestroyPlayerDroppedItem")
boolean destroyPlayerDroppedItem();
@Key("DestroyEquipableItem")
boolean destroyEquipableItem();
@Key("SaveDroppedItem")
boolean saveDroppedItem();
@Key("EmptyDroppedItemTableAfterLoad")
boolean emptyDroppedItemTableAfterLoad();
@Key("SaveDroppedItemInterval")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getSaveDroppedItemInterval();
@Key("ClearDroppedItemTable")
boolean clearDroppedItemTable();
@Key("AutoDeleteInvalidQuestData")
boolean autoDeleteInvalidQuestData();
@Key("PreciseDropCalculation")
boolean preciseDropCalculation();
@Key("PreciseDropMultipleGroupRolls")
boolean preciseDropMultipleGroupRolls();
@Key("PreciseDropMultipleRollsAggregateDrops")
boolean preciseDropMultipleRollsAggregateDrops();
@Key("MultipleItemDrop")
boolean multipleItemDrop();
@Key("ForceInventoryUpdate")
boolean forceInventoryUpdate();
@Key("LazyCache")
boolean lazyCache();
@Key("CacheCharNames")
boolean cacheCharNames();
@Key("MinNPCAnimation")
int getMinNPCAnimation();
@Key("MaxNPCAnimation")
int getMaxNPCAnimation();
@Key("MinMonsterAnimation")
int getMinMonsterAnimation();
@Key("MaxMonsterAnimation")
int getMaxMonsterAnimation();
@Key("MoveBasedKnownList")
boolean moveBasedKnownList();
@Key("KnownListUpdateInterval")
long getKnownListUpdateInterval();
@Key("CheckKnownList")
boolean checkKnownList();
@Key("GridsAlwaysOn")
boolean gridsAlwaysOn();
@Key("GridNeighborTurnOnTime")
int getGridNeighborTurnOnTime();
@Key("GridNeighborTurnOffTime")
int getGridNeighborTurnOffTime();
@Key("EnableFallingDamage")
boolean enableFallingDamage();
@Key("PeaceZoneMode")
int getPeaceZoneMode();
@Key("GlobalChat")
String getGlobalChat();
@Key("TradeChat")
String getTradeChat();
@Key("AllowWarehouse")
boolean allowWarehouse();
@Key("WarehouseCache")
boolean warehouseCache();
@Key("WarehouseCacheTime")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getWarehouseCacheTime();
@Key("AllowRefund")
boolean allowRefund();
@Key("AllowMail")
boolean allowMail();
@Key("AllowAttachments")
boolean allowAttachments();
@Key("AllowWear")
boolean allowWear();
@Key("WearDelay")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getWearDelay();
@Key("WearPrice")
int getWearPrice();
@Key("RestorePlayerInstance")
boolean restorePlayerInstance();
@Key("AllowSummonInInstance")
boolean allowSummonInInstance();
@Key("EjectDeadPlayerTime")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getEjectDeadPlayerTime();
@Key("InstanceFinishTime")
int getInstanceFinishTime();
// TODO(Zoey76): Implement AllowRace configuration.
@Key("AllowRace")
boolean allowRace();
@Key("AllowWater")
boolean allowWater();
@Key("AllowRentPet")
boolean allowRentPet();
@Key("AllowFishing")
boolean allowFishing();
@Key("AllowBoat")
boolean allowBoat();
@Key("BoatBroadcastRadius")
int getBoatBroadcastRadius();
@Key("AllowCursedWeapons")
boolean allowCursedWeapons();
@Key("AllowPetWalkers")
boolean allowPetWalkers();
@Key("ShowServerNews")
boolean showServerNews();
@Key("EnableCommunityBoard")
boolean enableCommunityBoard();
@Key("BBSDefault")
String getBBSDefault();
@Key("UseChatFilter")
boolean useChatFilter();
@Key("ChatFilterChars")
String getChatFilterChars();
@Key("ChatFilter")
Set<String> getChatFilter();
@Key("BanChatChannels")
Set<Integer> getBanChatChannels();
@Key("AllowManor")
boolean allowManor();
@Key("ManorRefreshTime")
int getManorRefreshTime();
@Key("ManorRefreshMin")
int getManorRefreshMin();
@Key("ManorApproveTime")
int getManorApproveTime();
@Key("ManorApproveMin")
int getManorApproveMin();
@Key("ManorMaintenanceMin")
int getManorMaintenanceMin();
@Key("ManorSaveAllActions")
boolean manorSaveAllActions();
@Key("ManorSavePeriodRate")
int getManorSavePeriodRate();
@Key("AllowLottery")
boolean allowLottery();
@Key("LotteryPrize")
long getLotteryPrize();
@Key("LotteryTicketPrice")
long getLotteryTicketPrice();
@Key("Lottery5NumberRate")
float getLottery5NumberRate();
@Key("Lottery4NumberRate")
float getLottery4NumberRate();
@Key("Lottery3NumberRate")
float getLottery3NumberRate();
@Key("Lottery2and1NumberPrize")
long getLottery2and1NumberPrize();
@Key("ItemAuctionEnabled")
boolean itemAuctionEnabled();
@Key("ItemAuctionExpiredAfter")
int getItemAuctionExpiredAfter();
@Key("ItemAuctionTimeExtendsOnBid")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getItemAuctionTimeExtendsOnBid();
// TODO(Zoey76): Implement RiftMinPartySize configuration.
@Key("RiftMinPartySize")
int getRiftMinPartySize();
@Key("MaxRiftJumps")
int getMaxRiftJumps();
@Key("RiftSpawnDelay")
int getRiftSpawnDelay();
@Key("AutoJumpsDelayMin")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getAutoJumpsDelayMin();
@Key("AutoJumpsDelayMax")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getAutoJumpsDelayMax();
@Key("BossRoomTimeMultiply")
float getBossRoomTimeMultiply();
// TODO(Zoey76): Implement RecruitCost configuration.
@Key("RecruitCost")
int getRecruitCost();
// TODO(Zoey76): Implement SoldierCost configuration.
@Key("SoldierCost")
int getSoldierCost();
// TODO(Zoey76): Implement OfficerCost configuration.
@Key("OfficerCost")
int getOfficerCost();
// TODO(Zoey76): Implement CaptainCost configuration.
@Key("CaptainCost")
int getCaptainCost();
// TODO(Zoey76): Implement CommanderCost configuration.
@Key("CommanderCost")
int getCommanderCost();
// TODO(Zoey76): Implement HeroCost configuration.
@Key("HeroCost")
int getHeroCost();
@Key("TimeOfAttack")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getTimeOfAttack();
// TODO(Zoey76): Implement TimeOfCoolDown configuration.
@Key("TimeOfCoolDown")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getTimeOfCoolDown();
@Key("TimeOfEntry")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getTimeOfEntry();
@Key("TimeOfWarmUp")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getTimeOfWarmUp();
// TODO(Zoey76): Move this four sepulchers to own configuration file.
@Key("NumberOfNecessaryPartyMembers")
int getNumberOfNecessaryPartyMembers();
@Key("DefaultPunish")
IllegalActionPunishmentType getDefaultPunish();
@Key("DefaultPunishParam")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getDefaultPunishParam();
@Key("OnlyGMItemsFree")
boolean onlyGMItemsFree();
@Key("JailIsPvp")
boolean jailIsPvp();
@Key("JailDisableChat")
boolean jailDisableChat();
@Key("JailDisableTransaction")
boolean jailDisableTransaction();
@Key("NormalEnchantCostMultipiler")
int getNormalEnchantCostMultipiler();
@Key("SafeEnchantCostMultipiler")
int getSafeEnchantCostMultipiler();
@Key("CustomSpawnlistTable")
boolean customSpawnlistTable();
@Key("SaveGmSpawnOnCustom")
boolean saveGmSpawnOnCustom();
@Key("CustomNpcData")
boolean customNpcData();
@Key("CustomTeleportTable")
boolean customTeleportTable();
@Key("CustomNpcBufferTables")
boolean customNpcBufferTables();
@Key("CustomSkillsLoad")
boolean customSkillsLoad();
@Key("CustomItemsLoad")
boolean customItemsLoad();
@Key("CustomMultisellLoad")
boolean customMultisellLoad();
@Key("CustomBuyListLoad")
boolean customBuyListLoad();
@Key("BirthdayGift")
int getBirthdayGift();
@Key("BirthdayMailSubject")
String getBirthdayMailSubject();
@Key("BirthdayMailText")
String getBirthdayMailText();
@Key("EnableBlockCheckerEvent")
boolean enableBlockCheckerEvent();
@Key("BlockCheckerMinTeamMembers")
int getBlockCheckerMinTeamMembers();
@Key("HBCEFairPlay")
boolean isHBCEFairPlay();
@Key("HellboundWithoutQuest")
boolean hellboundWithoutQuest();
@Key("EnableBotReportButton")
boolean enableBotReportButton();
@Key("BotReportPointsResetHour")
String getBotReportPointsResetHour();
@Key("BotReportDelay")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getBotReportDelay();
@Key("AllowReportsFromSameClanMembers")
boolean allowReportsFromSameClanMembers();
@Key("Debug")
boolean debug();
@Key("InstanceDebug")
boolean instanceDebug();
@Key("HtmlActionCacheDebug")
boolean htmlActionCacheDebug();
@Key("PacketHandlerDebug")
boolean packetHandlerDebug();
@Key("Developer")
boolean developer();
@Key("NoHandlers")
boolean noHandlers();
@Key("NoQuests")
boolean noQuests();
@Key("NoSpawns")
boolean noSpawns();
@Key("ShowQuestsLoadInLogs")
boolean showQuestsLoadInLogs();
// TODO(Zoey76): Implement ShowScriptsLoadInLogs configuration.
@Key("ShowScriptsLoadInLogs")
boolean showScriptsLoadInLogs();
}
@@ -0,0 +1,106 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.io.File;
import java.util.List;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
/**
* Geodata Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/geodata.properties",
"file:./config/geodata.properties",
"classpath:config/geodata.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface GeodataConfiguration extends Mutable, Reloadable {
/**
* Pathfinding options:
* <ul>
* <li>0 = Disabled</li>
* <li>1 = Enabled using path node files</li>
* <li>2 = Enabled using geodata cells at runtime</li>
* </ul>
* @return
*/
@Key("PathFinding")
int getPathFinding();
@Key("PathnodePath")
File getPathnodePath();
@Key("PathFindBuffers")
String getPathFindBuffers();
@Key("LowWeight")
float getLowWeight();
@Key("MediumWeight")
float getMediumWeight();
@Key("HighWeight")
float getHighWeight();
@Key("AdvancedDiagonalStrategy")
boolean advancedDiagonalStrategy();
@Key("DiagonalWeight")
float getDiagonalWeight();
@Key("MaxPostfilterPasses")
int getMaxPostfilterPasses();
@Key("DebugPath")
boolean debugPath();
@Key("ForceGeoData")
boolean forceGeoData();
@Key("CoordSynchronize")
int getCoordSynchronize();
@Key("GeoDataPath")
File getGeoDataPath();
@Key("TryLoadUnspecifiedRegions")
boolean tryLoadUnspecifiedRegions();
@Separator(";")
@Key("IncludedRegions")
List<String> getIncludedRegions();
@Separator(";")
@Key("ExcludedRegions")
List<String> getExcludedRegions();
}
@@ -0,0 +1,52 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Minutes2MillisecondsConverter;
/**
* Gracia Seeds Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/graciaseeds.properties",
"file:./config/graciaseeds.properties",
"classpath:config/graciaseeds.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface GraciaSeedsConfiguration extends Reloadable {
@Key("TiatKillCountForNextState")
int getTiatKillCountForNextState();
@Key("Stage2Length")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getStage2Length();
}
@@ -0,0 +1,135 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.Hours2MillisecondsConverter;
import com.l2jserver.gameserver.config.converter.Minutes2MillisecondsConverter;
/**
* Grand Boss Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/grandboss.properties",
"file:./config/grandboss.properties",
"classpath:config/grandboss.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface GrandBossConfiguration extends Reloadable {
@Key("AntharasWaitTime")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getAntharasWaitTime();
@Key("IntervalOfAntharasSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfAntharasSpawn();
@Key("RandomOfAntharasSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfAntharasSpawn();
@Key("ValakasWaitTime")
@ConverterClass(Minutes2MillisecondsConverter.class)
long getValakasWaitTime();
@Key("IntervalOfValakasSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfValakasSpawn();
@Key("RandomOfValakasSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfValakasSpawn();
@Key("IntervalOfBaiumSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfBaiumSpawn();
@Key("RandomOfBaiumSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfBaiumSpawn();
@Key("IntervalOfCoreSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfCoreSpawn();
@Key("RandomOfCoreSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfCoreSpawn();
@Key("IntervalOfOrfenSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfOrfenSpawn();
@Key("RandomOfOrfenSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfOrfenSpawn();
@Key("IntervalOfQueenAntSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfQueenAntSpawn();
@Key("RandomOfQueenAntSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfQueenAntSpawn();
@Key("IntervalOfBelethSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getIntervalOfBelethSpawn();
@Key("RandomOfBelethSpawn")
@ConverterClass(Hours2MillisecondsConverter.class)
long getRandomOfBelethSpawn();
@Key("BelethMinPlayers")
int getBelethMinPlayers();
// TODO(Zoey76): Implement AllowZakenWithoutParty configuration.
@Key("AllowZakenWithoutParty")
boolean getAllowZakenWithoutParty();
// TODO(Zoey76): Implement ZakenMinPlayers configuration.
@Key("ZakenMinPlayers")
List<Integer> getZakenMinPlayers();
// TODO(Zoey76): Implement ZakenMaxPlayers configuration.
@Key("ZakenMaxPlayers")
List<Integer> getZakenMaxPlayers();
// TODO(Zoey76): Implement ZakenMinPlayerLevel configuration.
@Key("ZakenMinPlayerLevel")
List<Integer> getZakenMinPlayerLevel();
// TODO(Zoey76): Implement ZakenCanBeRevealedByAOESpells configuration.
@Key("ZakenCanBeRevealedByAOESpells")
boolean getZakenCanBeRevealedByAoeSPells();
}
@@ -0,0 +1,60 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.math.BigInteger;
import org.aeonbits.owner.Accessible;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.HexIdConverter;
/**
* Hex Id Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/" + Configuration.CUSTOM_SUBPATH + HexIdConfiguration.FILENAME,
"file:" + Configuration.DEFAULT_PATH + HexIdConfiguration.FILENAME,
"classpath:" + Configuration.DEFAULT_PATH + HexIdConfiguration.FILENAME
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface HexIdConfiguration extends Mutable, Reloadable, Accessible {
String FILENAME = "hexid.txt";
String SERVERID_KEY = "ServerID";
String HEXID_KEY = "HexID";
@Key(SERVERID_KEY)
int getServerID();
@Key(HEXID_KEY)
@ConverterClass(HexIdConverter.class)
BigInteger getHexID();
}
@@ -0,0 +1,75 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Hunting System Configuration.
* @author Maneco2
* @version 2.6.3.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/huntingsystem.properties",
"file:./config/huntingsystem.properties",
"classpath:config/huntingsystem.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface HuntingSystemConfiguration extends Reloadable {
@Key("NevitEnable")
boolean getNevitEnable();
@Key("NevitBlessingMaxPoints")
int getNevitBlessingMaxPoints();
@Key("NevitBonusMaxTime")
int getHuntingBonusMaxTime();
@Key("NevitRegularPoints")
int getNevitRegularPoints();
@Key("NevitRegularPoints2")
int getNevitRegularPoints2();
@Key("NevitNormalPoints")
int getNevitNormalPoints();
@Key("NevitLevelAcquirePoints")
int getNevitLevelAcquirePoints();
@Key("NevitLevelAcquirePoints2")
int getNevitLevelAcquirePoints2();
@Key("NevitDecreaseVitalityAcquirePoints")
int getNevitDecreaseVitalityAcquirePoints();
@Key("NevitBonusEffectTime")
int getNevitBlessingEffetcTime();
@Key("HuntingSystemLimit")
boolean getHuntingBonusLimit();
}
@@ -0,0 +1,169 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static com.l2jserver.gameserver.GameServer.startTimedSection;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet6Address;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import com.l2jserver.gameserver.util.IXmlReader;
/**
* IP Config Data.
* @author Zoey76
* @version 2.6.1.0
*/
public class IPConfigData implements IXmlReader {
private static final Logger LOG = LoggerFactory.getLogger(IPConfigData.class);
private static final String IP_CONFIG_FILE = "./config/ipconfig.xml";
private final List<String> _subnets = new ArrayList<>(5);
private final List<String> _hosts = new ArrayList<>(5);
public IPConfigData() {
load();
}
@Override
public void load() {
startTimedSection("Network Configuration");
final File f = new File(IP_CONFIG_FILE);
if (f.exists()) {
LOG.info("Using existing ipconfig.xml.");
parseFile(new File(IP_CONFIG_FILE));
} else {
LOG.info("Using automatic network configuration.");
autoIpConfig();
}
}
@Override
public void parseDocument(Document doc) {
for (var n = doc.getFirstChild(); n != null; n = n.getNextSibling()) {
if ("gameserver".equalsIgnoreCase(n.getNodeName())) {
for (var d = n.getFirstChild(); d != null; d = d.getNextSibling()) {
if ("define".equalsIgnoreCase(d.getNodeName())) {
final var attrs = d.getAttributes();
_subnets.add(attrs.getNamedItem("subnet").getNodeValue());
_hosts.add(attrs.getNamedItem("address").getNodeValue());
if (_hosts.size() != _subnets.size()) {
LOG.warn("Failed to load {} file - subnets does not match server addresses.", IP_CONFIG_FILE);
}
}
}
final var att = n.getAttributes().getNamedItem("address");
if (att == null) {
LOG.warn("Failed to load {} file - default server address is missing.", IP_CONFIG_FILE);
_hosts.add("127.0.0.1");
} else {
_hosts.add(att.getNodeValue());
}
_subnets.add("0.0.0.0/0");
}
}
}
protected void autoIpConfig() {
String externalIp;
try {
final var autoIp = URI.create("http://ip1.dynupdate.no-ip.com:8245/").toURL();
try (var in = new BufferedReader(new InputStreamReader(autoIp.openStream()))) {
externalIp = in.readLine();
}
} catch (IOException ex) {
LOG.warn("Failed to connect to ip1.dynupdate.no-ip.com:8245 please check your internet connection using 127.0.0.1!");
externalIp = "127.0.0.1";
}
try {
final var niList = NetworkInterface.getNetworkInterfaces();
while (niList.hasMoreElements()) {
final var ni = niList.nextElement();
if (!ni.isUp() || ni.isVirtual()) {
continue;
}
if (!ni.isLoopback() && ((ni.getHardwareAddress() == null) || (ni.getHardwareAddress().length != 6))) {
continue;
}
for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
if (ia.getAddress() instanceof Inet6Address) {
continue;
}
final String hostAddress = ia.getAddress().getHostAddress();
final int subnetPrefixLength = ia.getNetworkPrefixLength();
final int subnetMaskInt = subnetPrefixLength == 0 ? 0 : -1 << (32 - subnetPrefixLength);
final int hostAddressInt = Arrays.stream(hostAddress.split("\\.")).mapToInt(Integer::parseInt).reduce((r, e) -> (r << 8) + e).orElse(0);
final int subnetAddressInt = hostAddressInt & subnetMaskInt;
final String subnetAddress = ((subnetAddressInt >> 24) & 0xFF) + "." + ((subnetAddressInt >> 16) & 0xFF) + "." + ((subnetAddressInt >> 8) & 0xFF) + "." + (subnetAddressInt & 0xFF);
final String subnet = subnetAddress + '/' + subnetPrefixLength;
if (!_subnets.contains(subnet) && !subnet.equals("0.0.0.0/0")) {
_subnets.add(subnet);
_hosts.add(hostAddress);
LOG.info("Adding new subnet: " + subnet + " address: " + hostAddress);
}
}
}
// External host and subnet
_hosts.add(externalIp);
_subnets.add("0.0.0.0/0");
LOG.info("Adding new subnet: 0.0.0.0/0 address: {}", externalIp);
} catch (SocketException e) {
LOG.error("Configuration failed please manually configure ipconfig.xml", e);
System.exit(0);
}
}
public List<String> getSubnets() {
if (_subnets.isEmpty()) {
return List.of("0.0.0.0/0");
}
return _subnets;
}
public List<String> getHosts() {
if (_hosts.isEmpty()) {
return List.of("127.0.0.1");
}
return _hosts;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* MMO Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/mmo.properties",
"file:./config/mmo.properties",
"classpath:config/mmo.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface MMOConfiguration extends Reloadable {
@Key("SleepTime")
int getSleepTime();
@Key("MaxSendPerPass")
int getMaxSendPerPass();
@Key("MaxReadPerPass")
int getMaxReadPerPass();
@Key("HelperBufferCount")
int getHelperBufferCount();
@Key("TcpNoDelay")
boolean isTcpNoDelay();
}
@@ -0,0 +1,204 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.MapIntegerIntegerConverter;
/**
* NPC Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/npc.properties",
"file:./config/npc.properties",
"classpath:config/npc.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface NPCConfiguration extends Reloadable {
@Key("AnnounceMammonSpawn")
boolean announceMammonSpawn();
@Key("MobAggroInPeaceZone")
boolean mobAggroInPeaceZone();
@Key("AttackableNpcs")
boolean attackableNpcs();
@Key("ViewNpc")
boolean viewNpc();
@Key("MaxDriftRange")
int getMaxDriftRange();
@Key("ShowNpcLevel")
boolean showNpcLevel();
@Key("ShowCrestWithoutQuest")
boolean showCrestWithoutQuest();
@Key("RandomEnchantEffect")
boolean randomEnchantEffect();
@Key("MinNPCLevelForDmgPenalty")
int getMinNPCLevelForDmgPenalty();
@Key("DmgPenaltyForLvLDifferences")
List<Double> getDmgPenaltyForLvLDifferences();
@Key("CritDmgPenaltyForLvLDifferences")
List<Double> getCritDmgPenaltyForLvLDifferences();
@Key("SkillDmgPenaltyForLvLDifferences")
List<Double> getSkillDmgPenaltyForLvLDifferences();
// TODO(Zoey76): Implement MinNPCLevelForMagicPenalty configuration.
@Key("MinNPCLevelForMagicPenalty")
int getMinNPCLevelForMagicPenalty();
@Key("SkillChancePenaltyForLvLDifferences")
List<Double> getSkillChancePenaltyForLvLDifferences();
// Monsters
// TODO(Zoey76): Implement DecayTimeTask configuration.
@Key("DecayTimeTask")
int getDecayTimeTask();
@Key("DefaultCorpseTime")
int getDefaultCorpseTime();
@Key("SpoiledCorpseExtendTime")
int getSpoiledCorpseExtendTime();
@Key("CorpseConsumeSkillAllowedTimeBeforeDecay")
int getCorpseConsumeSkillAllowedTimeBeforeDecay();
// Guards
@Key("GuardAttackAggroMob")
boolean guardAttackAggroMob();
// Pets
// TODO(Zoey76): Implement AllowWyvernUpgrader configuration.
@Key("AllowWyvernUpgrader")
int allowWyvernUpgrader();
@Key("PetRentNPCs")
Set<Integer> getPetRentNPCs();
@Key("MaximumSlotsForPet")
int getMaximumSlotsForPet();
@Key("PetHpRegenMultiplier")
double getPetHpRegenMultiplier();
@Key("PetMpRegenMultiplier")
double getPetMpRegenMultiplier();
// Raid Bosses
@Key("RaidHpRegenMultiplier")
double getRaidHpRegenMultiplier();
@Key("RaidMpRegenMultiplier")
double getRaidMpRegenMultiplier();
@Key("RaidPDefenceMultiplier")
double getRaidPDefenceMultiplier();
@Key("RaidMDefenceMultiplier")
double getRaidMDefenceMultiplier();
@Key("RaidPAttackMultiplier")
double getRaidPAttackMultiplier();
@Key("RaidMAttackMultiplier")
double getRaidMAttackMultiplier();
@Key("RaidMinRespawnMultiplier")
double getRaidMinRespawnMultiplier();
@Key("RaidMaxRespawnMultiplier")
double getRaidMaxRespawnMultiplier();
@Key("RaidMinionRespawnTime")
long getRaidMinionRespawnTime();
@Key("CustomMinionsRespawnTime")
@ConverterClass(MapIntegerIntegerConverter.class)
Map<Integer, Integer> getCustomMinionsRespawnTime();
@Key("RaidCurse")
boolean raidCurse();
@Key("RaidChaosTime")
int getRaidChaosTime();
@Key("GrandChaosTime")
int getGrandChaosTime();
@Key("MinionChaosTime")
int getMinionChaosTime();
// Drops
@Key("UseDeepBlueDropRules")
boolean useDeepBlueDropRules();
@Key("UseDeepBlueDropRulesRaid")
boolean useDeepBlueDropRulesRaid();
@Key("DropAdenaMinLevelDifference")
int getDropAdenaMinLevelDifference();
@Key("DropAdenaMaxLevelDifference")
int getDropAdenaMaxLevelDifference();
@Key("DropAdenaMinLevelGapChance")
int getDropAdenaMinLevelGapChance();
@Key("DropItemMinLevelDifference")
int getDropItemMinLevelDifference();
@Key("DropItemMaxLevelDifference")
int getDropItemMaxLevelDifference();
@Key("DropItemMinLevelGapChance")
int getDropItemMinLevelGapChance();
@Key("MaxAggroRange")
int getMaxAggroRange();
}
@@ -0,0 +1,189 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.ItemHolderConverter;
import com.l2jserver.gameserver.model.holders.ItemHolder;
/**
* Olympiad Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/olympiad.properties",
"file:./config/olympiad.properties",
"classpath:config/olympiad.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface OlympiadConfiguration extends Reloadable {
@Key("StartHour")
int getStartHour();
@Key("StartMinute")
int getStartMinute();
@Key("MaxBuffs")
int getMaxBuffs();
// TODO(Zoey76): Check if this should be in minutes or other time unit.
@Key("CompetitionPeriod")
int getCompetitionPeriod();
@Key("BattlePeriod")
int getBattlePeriod();
// TODO(Zoey76): Check if this should be in minutes or other time unit.
@Key("WeeklyPeriod")
int getWeeklyPeriod();
// TODO(Zoey76): Check if this should be in minutes or other time unit.
@Key("ValidationPeriod")
int getValidationPeriod();
@Key("StartPoints")
int getStartPoints();
@Key("WeeklyPoints")
int getWeeklyPoints();
@Key("ClassedParticipants")
int getClassedParticipants();
@Key("NonClassedParticipants")
int getNonClassedParticipants();
@Key("TeamsParticipants")
int getTeamsParticipants();
@Key("RegistrationDisplayNumber")
int getRegistrationDisplayNumber();
@Separator(";")
@Key("ClassedReward")
@ConverterClass(ItemHolderConverter.class)
List<ItemHolder> getClassedReward();
@Separator(";")
@Key("NonClassedReward")
@ConverterClass(ItemHolderConverter.class)
List<ItemHolder> getNonClassedReward();
@Separator(";")
@Key("TeamReward")
@ConverterClass(ItemHolderConverter.class)
List<ItemHolder> getTeamReward();
@Key("CompetitionRewardItem")
int getCompetitionRewardItem();
@Key("MinMatchesForPoints")
int getMinMatchesForPoints();
@Key("GPPerPoint")
int getGPPerPoint();
@Key("HeroPoints")
int getHeroPoints();
@Key("Rank1Points")
int getRank1Points();
@Key("Rank2Points")
int getRank2Points();
@Key("Rank3Points")
int getRank3Points();
@Key("Rank4Points")
int getRank4Points();
@Key("Rank5Points")
int getRank5Points();
@Key("MaxPoints")
int getMaxPoints();
@Key("ShowMonthlyWinners")
boolean showMonthlyWinners();
@Key("AnnounceGames")
boolean announceGames();
@Key("RestrictedItems")
Set<Integer> getRestrictedItems();
@Key("EnchantLimit")
int getEnchantLimit();
@Key("LogFights")
boolean logFights();
@Key("WaitTime")
int getWaitTime();
@Key("DividerClassed")
int getDividerClassed();
@Key("DividerNonClassed")
int getDividerNonClassed();
@Key("MaxWeeklyMatches")
int getMaxWeeklyMatches();
@Key("MaxWeeklyMatchesNonClassed")
int getMaxWeeklyMatchesNonClassed();
@Key("MaxWeeklyMatchesClassed")
int getMaxWeeklyMatchesClassed();
@Key("MaxWeeklyMatchesTeam")
int getMaxWeeklyMatchesTeam();
// TODO(Zoey76): Change this so the check doesn't depend on a null value.
@Key("CurrentCycle")
Integer getCurrentCycle();
@Key("Period")
int getPeriod();
// TODO(Zoey76): Check if this should be in minutes or other time unit.
@Key("OlympiadEnd")
long getOlympiadEnd();
@Key("ValidationEnd")
long getValidationEnd();
@Key("NextWeeklyChange")
long getNextWeeklyChange();
}
@@ -0,0 +1,66 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* PvP Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/pvp.properties",
"file:./config/pvp.properties",
"classpath:config/pvp.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface PvPConfiguration extends Reloadable {
@Key("CanGMDropEquipment")
boolean canGMDropEquipment();
@Key("PetItems")
Set<Integer> getPetItems();
@Key("NonDroppableItems")
Set<Integer> getNonDroppableItems();
@Key("MinimumPKRequiredToDrop")
int getMinimumPKRequiredToDrop();
@Key("AwardPKKillPVPPoint")
boolean awardPKKillPVPPoint();
@Key("PvPVsNormalTime")
int getPvPVsNormalTime();
@Key("PvPVsPvPTime")
int getPvPVsPvPTime();
}
@@ -0,0 +1,186 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.Map;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.MapIntegerFloatConverter;
/**
* Rates Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/rates.properties",
"file:./config/rates.properties",
"classpath:config/rates.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface RatesConfiguration extends Reloadable {
@Key("DeathDropAmountMultiplier")
double getDeathDropAmountMultiplier();
@Key("CorpseDropAmountMultiplier")
double getCorpseDropAmountMultiplier();
@Key("HerbDropAmountMultiplier")
double getHerbDropAmountMultiplier();
@Key("RaidDropAmountMultiplier")
double getRaidDropAmountMultiplier();
@Key("DeathDropChanceMultiplier")
double getDeathDropChanceMultiplier();
@Key("CorpseDropChanceMultiplier")
double getCorpseDropChanceMultiplier();
@Key("HerbDropChanceMultiplier")
double getHerbDropChanceMultiplier();
@Key("RaidDropChanceMultiplier")
double getRaidDropChanceMultiplier();
@Key("DropAmountMultiplierByItemId")
@ConverterClass(MapIntegerFloatConverter.class)
Map<Integer, Float> getDropAmountMultiplierByItemId();
@Key("DropChanceMultiplierByItemId")
@ConverterClass(MapIntegerFloatConverter.class)
Map<Integer, Float> getDropChanceMultiplierByItemId();
@Key("RateXp")
float getRateXp();
@Key("RateSp")
float getRateSp();
@Key("RatePartyXp")
float getRatePartyXp();
@Key("RatePartySp")
float getRatePartySp();
// TODO(Zoey76): Should this be int instead of float?
@Key("RateDropManor")
int getRateDropManor();
@Key("RateKarmaLost")
double getRateKarmaLost();
@Key("RateKarmaExpLost")
double getRateKarmaExpLost();
@Key("RateSiegeGuardsPrice")
double getRateSiegeGuardsPrice();
@Key("RateExtractable")
float getRateExtractable();
@Key("RateHellboundTrustIncrease")
float getRateHellboundTrustIncrease();
@Key("RateHellboundTrustDecrease")
float getRateHellboundTrustDecrease();
@Key("QuestDropChanceMultiplier")
float getQuestDropChanceMultiplier();
@Key("QuestDropAmountMultiplier")
float getQuestDropAmountMultiplier();
@Key("RateQuestRewardXP")
float getRateQuestRewardXP();
@Key("RateQuestRewardSP")
float getRateQuestRewardSP();
@Key("RateQuestRewardAdena")
float getRateQuestRewardAdena();
@Key("UseQuestRewardMultipliers")
boolean useQuestRewardMultipliers();
@Key("RateQuestReward")
float getRateQuestReward();
@Key("RateQuestRewardPotion")
float getRateQuestRewardPotion();
@Key("RateQuestRewardScroll")
float getRateQuestRewardScroll();
@Key("RateQuestRewardRecipe")
float getRateQuestRewardRecipe();
@Key("RateQuestRewardMaterial")
float getRateQuestRewardMaterial();
@Key("PlayerDropLimit")
int getPlayerDropLimit();
@Key("PlayerRateDrop")
int getPlayerRateDrop();
@Key("PlayerRateDropItem")
int getPlayerRateDropItem();
@Key("PlayerRateDropEquip")
int getPlayerRateDropEquip();
@Key("PlayerRateDropEquipWeapon")
int getPlayerRateDropEquipWeapon();
@Key("KarmaDropLimit")
int getKarmaDropLimit();
@Key("KarmaRateDrop")
int getKarmaRateDrop();
@Key("KarmaRateDropItem")
int getKarmaRateDropItem();
@Key("KarmaRateDropEquip")
int getKarmaRateDropEquip();
@Key("KarmaRateDropEquipWeapon")
int getKarmaRateDropEquipWeapon();
@Key("PetXpRate")
double getPetXpRate();
// TODO(Zoey76): Should PetFoodRate be float?
@Key("PetFoodRate")
int getPetFoodRate();
@Key("SinEaterXpRate")
double getSinEaterXpRate();
}
@@ -0,0 +1,98 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.io.File;
import java.util.Set;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Mutable;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.FileConverter;
import com.l2jserver.gameserver.idfactory.IdFactoryType;
/**
* Server Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/server.properties",
"file:./config/server.properties",
"classpath:config/server.properties",
"system:properties",
"system:env"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface ServerConfiguration extends Mutable, Reloadable {
@Key("EnableUPnP")
boolean enableUPnP();
@Key("LoginHost")
String getLoginHost();
@Key("LoginPort")
int getLoginPort();
@Key("Host")
String getHost();
@Key("Port")
int getPort();
@Key("RequestServerId")
int getRequestServerId();
@Key("AcceptAlternateId")
boolean acceptAlternateId();
@Key("ReserveHostOnLogin")
boolean reserveHostOnLogin();
@Key("DatapackRoot")
@ConverterClass(FileConverter.class)
File getDatapackRoot();
@Key("ScriptRoot")
File getScriptRoot();
@Key("MaxOnlineUsers")
int getMaxOnlineUsers();
@Key("AllowedProtocolRevisions")
Set<Integer> getAllowedProtocolRevisions();
@Key("IdFactory")
IdFactoryType getIdFactory();
@Key("BadIdChecking")
boolean badIdChecking();
@Key("L2J_HOME")
String getL2jHome();
}
@@ -0,0 +1,119 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Seven Signs Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/sevensigns.properties",
"file:./config/sevensigns.properties",
"classpath:config/sevensigns.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface SevenSignsConfiguration extends Reloadable {
// TODO(Zoey76): Implement RequireClanCastle configuration.
@Key("RequireClanCastle")
boolean requireClanCastle();
// TODO(Zoey76): Implement CastleForDawn configuration.
@Key("CastleForDawn")
boolean castleForDawn();
// TODO(Zoey76): Implement CastleForDusk configuration.
@Key("CastleForDusk")
boolean castleForDusk();
@Key("FestivalMinPlayer")
int getFestivalMinPlayer();
@Key("MaxPlayerContrib")
int getMaxPlayerContrib();
// TODO(Zoey76): Convert to milliseconds.
@Key("FestivalManagerStart")
int getFestivalManagerStart();
@Key("FestivalLength")
int getFestivalLength();
@Key("FestivalCycleLength")
int getFestivalCycleLength();
@Key("FestivalFirstSpawn")
int getFestivalFirstSpawn();
@Key("FestivalFirstSwarm")
int getFestivalFirstSwarm();
@Key("FestivalSecondSpawn")
int getFestivalSecondSpawn();
@Key("FestivalSecondSwarm")
int getFestivalSecondSwarm();
@Key("FestivalChestSpawn")
int getFestivalChestSpawn();
@Key("DawnGatesPdefMult")
double getDawnGatesPdefMult();
@Key("DuskGatesPdefMult")
double getDuskGatesPdefMult();
@Key("DawnGatesMdefMult")
double getDawnGatesMdefMult();
@Key("DuskGatesMdefMult")
double getDuskGatesMdefMult();
@Key("StrictSevenSigns")
boolean strictSevenSigns();
@Key("SevenSignsLazyUpdate")
boolean sevenSignsLazyUpdate();
@Key("SevenSignsDawnTicketQuantity")
int getSevenSignsDawnTicketQuantity();
@Key("SevenSignsDawnTicketPrice")
int getSevenSignsDawnTicketPrice();
@Key("SevenSignsDawnTicketBundle")
int getSevenSignsDawnTicketBundle();
@Key("SevenSignsManorsAgreementId")
int getSevenSignsManorsAgreementId();
@Key("SevenSignsJoinDawnFee")
int getSevenSignsJoinDawnFee();
}
@@ -0,0 +1,275 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.ControlTowerConverter;
import com.l2jserver.gameserver.config.converter.FlameTowerConverter;
import com.l2jserver.gameserver.model.TowerSpawn;
/**
* Siege Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/siege.properties",
"file:./config/siege.properties",
"classpath:config/siege.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface SiegeConfiguration extends Reloadable {
@Key("SiegeLength")
int getSiegeLength();
@Key("MaxFlags")
int getMaxFlags();
@Key("ClanMinLevel")
int getClanMinLevel();
@Key("AttackerMaxClans")
int getAttackerMaxClans();
@Key("DefenderMaxClans")
int getDefenderMaxClans();
@Key("AttackerRespawn")
int getAttackerRespawn();
@Key("BloodAllianceReward")
int getBloodAllianceReward();
@Key("GludioFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGludioFlameTower1();
@Key("GludioFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGludioFlameTower2();
@Key("GludioControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGludioControlTower1();
@Key("GludioControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGludioControlTower2();
@Key("GludioControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGludioControlTower3();
@Key("GludioMaxMercenaries")
int getGludioMaxMercenaries();
@Key("GiranFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGiranFlameTower1();
@Key("GiranFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGiranFlameTower2();
@Key("GiranControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGiranControlTower1();
@Key("GiranControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGiranControlTower2();
@Key("GiranControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGiranControlTower3();
@Key("GiranMaxMercenaries")
int getGiranMaxMercenaries();
@Key("DionFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getDionFlameTower1();
@Key("DionFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getDionFlameTower2();
@Key("DionControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getDionControlTower1();
@Key("DionControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getDionControlTower2();
@Key("DionControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getDionControlTower3();
@Key("DionMaxMercenaries")
int getDionMaxMercenaries();
@Key("OrenFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getOrenFlameTower1();
@Key("OrenFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getOrenFlameTower2();
@Key("OrenControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getOrenControlTower1();
@Key("OrenControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getOrenControlTower2();
@Key("OrenControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getOrenControlTower3();
@Key("OrenMaxMercenaries")
int getOrenMaxMercenaries();
@Key("AdenFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getAdenFlameTower1();
@Key("AdenFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn AdenFlameTower2();
@Key("AdenControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getAdenControlTower1();
@Key("AdenControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getAdenControlTower2();
@Key("AdenControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getAdenControlTower3();
@Key("AdenMaxMercenaries")
int getAdenMaxMercenaries();
@Key("InnadrilFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getInnadrilFlameTower1();
@Key("InnadrilFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getInnadrilFlameTower2();
@Key("InnadrilControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getInnadrilControlTower1();
@Key("InnadrilControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getInnadrilControlTower2();
@Key("InnadrilControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getInnadrilControlTower3();
@Key("InnadrilMaxMercenaries")
int getInnadrilMaxMercenaries();
@Key("GoddardFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGoddardFlameTower1();
@Key("GoddardFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getGoddardFlameTower2();
@Key("GoddardControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGoddardControlTower1();
@Key("GoddardControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGoddardControlTower2();
@Key("GoddardControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getGoddardControlTower3();
@Key("GoddardMaxMercenaries")
int getGoddardMaxMercenaries();
@Key("RuneFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getRuneFlameTower1();
@Key("RuneFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getRuneFlameTower2();
@Key("RuneControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getRuneControlTower1();
@Key("RuneControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getRuneControlTower2();
@Key("RuneControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getRuneControlTower3();
@Key("RuneMaxMercenaries")
int getRuneMaxMercenaries();
@Key("SchuttgartFlameTower1")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getSchuttgartFlameTower1();
@Key("SchuttgartFlameTower2")
@ConverterClass(FlameTowerConverter.class)
TowerSpawn getSchuttgartFlameTower2();
@Key("SchuttgartControlTower1")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getSchuttgartControlTower1();
@Key("SchuttgartControlTower2")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getSchuttgartControlTower2();
@Key("SchuttgartControlTower3")
@ConverterClass(ControlTowerConverter.class)
TowerSpawn getSchuttgartControlTower3();
@Key("SchuttgartMaxMercenaries")
int getSchuttgartMaxMercenaries();
}
@@ -0,0 +1,81 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.model.zone.ZoneId;
/**
* Teleporter Service Configuration.
* @author HorridoJoho
* @version 2.6.2.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/teleporterservice.properties",
"file:./config/teleporterservice.properties",
"classpath:config/teleporterservice.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface TeleporterServiceConfiguration extends Reloadable {
@Key("Enable")
boolean enable();
@Key("Debug")
boolean getDebug();
@Key("ForbidInZones")
ZoneId[] getForbidInZones();
@Key("ForbidInEvents")
boolean getForbidInEvents();
@Key("ForbidInDuel")
boolean getForbidInDuel();
@Key("ForbidInFight")
boolean getForbidInFight();
@Key("ForbidInPvp")
boolean getForbidInPvp();
@Key("ForbidForChaoticPlayers")
boolean getForbidForChaoticPlayers();
@Key("VoicedEnable")
boolean getVoicedEnable();
@Key("VoicedCommand")
String getVoicedCommand();
@Key("VoicedName")
String getVoicedName();
@Key("VoicedRequiredItem")
int getVoicedRequiredItem();
}
@@ -0,0 +1,57 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Telnet Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/telnet.properties",
"file:./config/telnet.properties",
"classpath:config/telnet.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface TelnetConfiguration extends Reloadable {
@Key("EnableTelnet")
boolean isEnabled();
@Key("Port")
int getPort();
@Key("Password")
String getPassword();
@Key("Hosts")
List<String> getHosts();
}
@@ -0,0 +1,80 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Territory War Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/territorywar.properties",
"file:./config/territorywar.properties",
"classpath:config/territorywar.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface TerritoryWarConfiguration extends Reloadable {
// TODO(Zoey76): Convert this to milliseconds.
@Key("WarLength")
long getWarLength();
// TODO(Zoey76): Implement ClanMinLevel configuration.
@Key("ClanMinLevel")
int getClanMinLevel();
@Key("PlayerMinLevel")
int getPlayerMinLevel();
// TODO(Zoey76): Implement DefenderMaxClans configuration.
@Key("DefenderMaxClans")
int getDefenderMaxClans();
// TODO(Zoey76): Implement DefenderMaxPlayers configuration.
@Key("DefenderMaxPlayers")
int getDefenderMaxPlayers();
@Key("PlayerWithWardCanBeKilledInPeaceZone")
boolean playerWithWardCanBeKilledInPeaceZone();
@Key("SpawnWardsWhenTWIsNotInProgress")
boolean spawnWardsWhenTWIsNotInProgress();
@Key("ReturnWardsWhenTWStarts")
boolean returnWardsWhenTWStarts();
@Key("MinTerritoryBadgeForNobless")
int getMinTerritoryBadgeForNobless();
@Key("MinTerritoryBadgeForStriders")
int getMinTerritoryBadgeForStriders();
@Key("MinTerritoryBadgeForBigStrider")
int getMinTerritoryBadgeForBigStrider();
}
@@ -0,0 +1,164 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import java.util.List;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
import com.l2jserver.gameserver.config.converter.ItemHolderConverter;
import com.l2jserver.gameserver.config.converter.LocationConverter;
import com.l2jserver.gameserver.config.converter.Seconds2MillisecondsConverter;
import com.l2jserver.gameserver.config.converter.SkillHolderConverter;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.holders.ItemHolder;
import com.l2jserver.gameserver.model.holders.SkillHolder;
/**
* TvT Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/tvt.properties",
"file:./config/tvt.properties",
"classpath:config/tvt.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface TvTConfiguration extends Reloadable {
@Key("Enabled")
boolean enabled();
@Key("Instanced")
boolean instanced();
@Key("InstanceFile")
String getInstanceFile();
@Key("Interval")
List<String> getInterval();
// TODO(Zoey76): Convert to milliseconds.
@Key("ParticipationTime")
int getParticipationTime();
// TODO(Zoey76): Convert to milliseconds.
@Key("RunningTime")
int getRunningTime();
@Key("ParticipationNpcId")
int getParticipationNpcId();
@Key("ParticipationFee")
@ConverterClass(ItemHolderConverter.class)
ItemHolder getParticipationFee();
@Key("ParticipationNpcLoc")
@ConverterClass(LocationConverter.class)
Location getParticipationNpcLoc();
@Key("MinPlayersInTeams")
int getMinPlayersInTeams();
@Key("MaxPlayersInTeams")
int getMaxPlayersInTeams();
@Key("MinPlayerLevel")
int getMinPlayerLevel();
@Key("MaxPlayerLevel")
int getMaxPlayerLevel();
@Key("RespawnTeleportDelay")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getRespawnTeleportDelay();
@Key("StartLeaveTeleportDelay")
@ConverterClass(Seconds2MillisecondsConverter.class)
long getStartLeaveTeleportDelay();
@Key("Team1Name")
String getTeam1Name();
@Key("Team1Loc")
@ConverterClass(LocationConverter.class)
Location getTeam1Loc();
@Key("Team2Name")
String getTeam2Name();
@Key("Team2Loc")
@ConverterClass(LocationConverter.class)
Location getTeam2Loc();
@Separator(";")
@Key("Reward")
@ConverterClass(ItemHolderConverter.class)
List<ItemHolder> getReward();
@Key("AllowTargetTeamMember")
boolean allowTargetTeamMember();
@Key("AllowScroll")
boolean allowScroll();
@Key("AllowPotion")
boolean allowPotion();
// TODO(Zoey76): Implement AllowSummonByItem configuration.
@Key("AllowSummonByItem")
boolean allowSummonByItem();
@Key("DoorsToOpen")
List<Integer> getDoorsToOpen();
@Key("DoorsToClose")
List<Integer> getDoorsToClose();
@Key("RewardTeamTie")
boolean rewardTeamTie();
@Key("EffectsRemoval")
int getEffectsRemoval();
@Separator(";")
@Key("FighterBuffs")
@ConverterClass(SkillHolderConverter.class)
List<SkillHolder> getFighterBuffs();
@Separator(";")
@Key("MageBuffs")
@ConverterClass(SkillHolderConverter.class)
List<SkillHolder> getMageBuffs();
@Key("MaxParticipantsPerIP")
int getMaxParticipantsPerIP();
@Key("AllowVoicedInfoCommand")
boolean allowVoicedInfoCommand();
}
@@ -0,0 +1,76 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.aeonbits.owner.Config.HotReloadType.ASYNC;
import static org.aeonbits.owner.Config.LoadType.MERGE;
import org.aeonbits.owner.Config.HotReload;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.Sources;
import org.aeonbits.owner.Reloadable;
/**
* Vitality Configuration.
* @author Zoey76
* @version 2.6.1.0
*/
@Sources({
"file:${L2J_HOME}/custom/game/config/vitality.properties",
"file:./config/vitality.properties",
"classpath:config/vitality.properties"
})
@LoadPolicy(MERGE)
@HotReload(value = 20, unit = MINUTES, type = ASYNC)
public interface VitalityConfiguration extends Reloadable {
@Key("Enabled")
boolean enabled();
@Key("RecoverVitalityOnReconnect")
boolean recoverVitalityOnReconnect();
@Key("StartingVitalityPoints")
int getStartingVitalityPoints();
@Key("RateVitalityLevel1")
float getRateVitalityLevel1();
@Key("RateVitalityLevel2")
float getRateVitalityLevel2();
@Key("RateVitalityLevel3")
float getRateVitalityLevel3();
@Key("RateVitalityLevel4")
float getRateVitalityLevel4();
@Key("RateVitalityGain")
float getRateVitalityGain();
@Key("RateVitalityLost")
float getRateVitalityLost();
@Key("RateRecoveryPeaceZone")
float getRateRecoveryPeaceZone();
@Key("RateRecoveryOnReconnect")
float getRateRecoveryOnReconnect();
}
@@ -0,0 +1,64 @@
/*
* Copyright © 2004-2026 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.gameserver.config.converter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.l2jserver.gameserver.model.holders.ItemHolder;
/**
* Class Master Setting.
* @author Zoey76
* @version 2.6.1.0
*/
public class ClassMasterSetting {
private final Map<Integer, List<ItemHolder>> claimItems = new HashMap<>(3);
private final Map<Integer, List<ItemHolder>> rewardItems = new HashMap<>(3);
private final Map<Integer, Boolean> allowedClassChange = new HashMap<>(3);
public void addClaimItems(Integer job, List<ItemHolder> items) {
claimItems.put(job, items);
}
public void addRewardItems(Integer job, List<ItemHolder> items) {
rewardItems.put(job, items);
}
public void addAllowedClassChange(Integer job) {
allowedClassChange.put(job, true);
}
public boolean isAllowed(int job) {
return allowedClassChange.getOrDefault(job, false);
}
public List<ItemHolder> getRewardItems(int job) {
return rewardItems.getOrDefault(job, Collections.emptyList());
}
public List<ItemHolder> getRequireItems(int job) {
return claimItems.getOrDefault(job, Collections.emptyList());
}
}

Some files were not shown because too many files have changed in this diff Show More