Release v2.0.0-ALPHA (Merge pull request #2 from songoda/full-recode)

This commit is contained in:
Christian Koop 2021-07-20 18:57:41 +02:00 committed by GitHub
commit 273844b0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2988 additions and 1067 deletions

BIN
.DS_Store vendored

Binary file not shown.

311
.editorconfig Normal file
View File

@ -0,0 +1,311 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
# max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = none
ij_wrap_on_typing = false
[*.java]
ij_smart_tabs = true
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_at_first_column = true
ij_java_builder_methods = none
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 15
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = true
ij_java_doc_add_blank_line_after_return = true
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = true
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = true
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = *, |, javax.**, java.**, |, $*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 0
ij_java_keep_blank_lines_between_package_declaration_and_header = 0
ij_java_keep_blank_lines_in_code = 1
ij_java_keep_blank_lines_in_declarations = 1
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = true
ij_java_keep_simple_lambdas_in_one_line = true
ij_java_keep_simple_methods_in_one_line = true
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_at_first_column = true
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_names_count_to_use_import_on_demand = 9
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand = _java.awt.*, _javax.swing.*
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = true
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_record_header = false
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = true
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = true
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = true
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[*.properties]
ij_properties_align_group_field_declarations = false
ij_properties_keep_blank_lines = false
ij_properties_key_value_delimiter = equals
ij_properties_spaces_around_key_value_delimiter = false
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.pom, *.rng, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}]
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = true
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
ij_xml_keep_whitespaces_inside_cdata = false
ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
[{*.markdown, *.md}]
indent_size = 2
tab_width = 2
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[{*.yaml, *.yml, *.lang}]
indent_size = 2
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: songoda
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "maven" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"

96
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,96 @@
name: 'Build & Test'
on:
push:
branches: [ master, development, full-recode ]
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
# Setup Java
- uses: actions/setup-java@v2
with:
java-version: 16
distribution: adopt
# Checkout project files
- uses: actions/checkout@v2
# Caches
- name: 'Cache: Maven'
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
# Build project
- name: Build with Maven
run: 'mvn -B -Duser.name="GitHub Runner on $GITHUB_REPOSITORY (id=$GITHUB_RUN_ID)" clean package'
# Upload build artifacts
- name: 'Upload Build Artifact: EpicAnchors-*.jar'
uses: actions/upload-artifact@v2
with:
name: EpicAnchors-artifacts
path: ./target/EpicAnchors-*.jar
##
# Discord Webhook
# TODO: Extract into external Action for better re-usability (and readability) [Copied SongodaCore]
##
- name: 'Discord Webhook (Success)'
if: ${{ success() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development' || github.ref == 'refs/heads/full-recode') }}
continue-on-error: true
run: |
curl -X POST --data "{\"content\":null,\"embeds\":[{\"title\":\"Build succeeded!\",\"description\":\"The build with the ID #$GITHUB_RUN_NUMBER has succeeded!\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"color\":5490477,\"fields\":[{\"name\":\"Branch\",\"value\":\"$GITHUB_REF\",\"inline\":true}],\"author\":{\"name\":\"$GITHUB_REPOSITORY\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY\",\"icon_url\":\"$GITHUB_SERVER_URL/songoda.png\"},\"footer\":{\"text\":\"Initiated by $GITHUB_ACTOR\",\"icon_url\":\"$GITHUB_SERVER_URL/$GITHUB_ACTOR.png\"}}],\"username\":\"OctoAgent\",\"avatar_url\":\"https://github.githubassets.com/images/modules/logos_page/Octocat.png\"}" --header 'Content-Type: application/json' $DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BUILD_STATUS_WEBHOOK }}
- name: 'Discord Webhook (Failure)'
if: ${{ failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development' || github.ref == 'refs/heads/full-recode') }}
continue-on-error: true
run: |
curl -X POST --data "{\"content\":null,\"embeds\":[{\"title\":\"Build failed!\",\"description\":\"The build with the ID #$GITHUB_RUN_NUMBER has failed!\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"color\":15611419,\"fields\":[{\"name\":\"Branch\",\"value\":\"$GITHUB_REF\",\"inline\":true}],\"author\":{\"name\":\"$GITHUB_REPOSITORY\",\"url\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY\",\"icon_url\":\"$GITHUB_SERVER_URL/songoda.png\"},\"footer\":{\"text\":\"Initiated by $GITHUB_ACTOR\",\"icon_url\":\"$GITHUB_SERVER_URL/$GITHUB_ACTOR.png\"}}],\"username\":\"OctoAgent\",\"avatar_url\":\"https://github.githubassets.com/images/modules/logos_page/Octocat.png\"}" --header "Content-Type:application/json" $DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BUILD_STATUS_WEBHOOK }}
sonarcloud:
name: SonarCloud
runs-on: ubuntu-latest
steps:
# Setup Java
- uses: actions/setup-java@v2
with:
java-version: 11
distribution: adopt
# Checkout project files
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# Caches
- name: 'Cache: Maven'
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: 'Cache: SonarCloud'
uses: actions/cache@v2
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
# SonarCloud static analysis
- name: SonarCloud
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar

8
.gitignore vendored
View File

@ -1,3 +1,5 @@
.idea/*
target/*
EpicAnchors.iml
/target/
# JetBrains IDEs
/.idea/
*.iml

View File

@ -1,4 +1,4 @@
Copyright (c) 2018 Brianna OKeefe
Copyright (c) 2021 Songoda
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software with minimal restriction, including the rights to use, copy, modify or merge while excluding the rights to publish, (re)distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@ -6,4 +6,4 @@ The same distribution rights and limitations above shall similarly apply to any
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,5 +0,0 @@
# EpicAnchors
EpicAnchors allows your players to keep chunks loaded for a limited amount of time allowing redstone machines, mob grinders, spawners, and hoppers to all remain activated while the player is either out of normal range or offline.
![N|Solid](https://i.imgur.com/jKtE7ZM.png)

53
README.md Normal file
View File

@ -0,0 +1,53 @@
<!--suppress HtmlDeprecatedAttribute -->
<div align="center">
<img alt="EpicAnchors" src="https://cdn2.songoda.com/products/epicanchors/xTjYNZqmo1pVKZ1Za8YiLojGB6uM4bm6Bb0M5Spu.gif">
# EpicAnchors
**Allow your players to keep chunks loaded for a limited amount of time for a cost.**
**Integrates with EpicSpawners to keep your mobs spawning even when youre offline.**
<!-- Shields -->
[![Discord](https://img.shields.io/discord/293212540723396608?color=7289DA&label=Discord&logo=discord&logoColor=7289DA)](https://discord.gg/songoda)
[![Patreon](https://img.shields.io/badge/-Support_on_Patreon-F96854.svg?logo=patreon&style=flat&logoColor=white)](https://www.patreon.com/join/songoda)
<br>
[![Latest version](https://img.shields.io/badge/dynamic/xml?style=flat&color=blue&logo=github&logoColor=white&label=Latest&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsongoda%2FEpicAnchors%2Fmaster%2Fpom.xml&query=%2F*%5Blocal-name()%3D'project'%5D%2F*%5Blocal-name()%3D'version'%5D)](https://songoda.com/marketplace/product/31)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=songoda_EpicAnchors&metric=alert_status)](https://sonarcloud.io/dashboard?id=songoda_EpicAnchors)
[![GitHub last commit](https://img.shields.io/github/last-commit/songoda/EpicAnchors?label=Last+commit)](https://github.com/songoda/EpicAnchors/commits)
<br>
[![bStats Servers](https://img.shields.io/bstats/servers/4816?label=Servers)](https://bstats.org/plugin/bukkit/EpicAnchors/4816)
</div>
## Table of Contents
* [Introduction](#introduction)
* [Marketplace](#marketplace)
* [Documentation](#documentation)
* [Support](#support)
* [Suggestions](#suggestions)
## Introduction
`// TODO: Write an introduction`
## Marketplace
You can visit [our marketplace](https://songoda.com/marketplace/product/31) to download EpicAnchors as well as take a
look at many other fantastic plugins which are sure to catch your eye.
## Documentation
You can find all the information about EpicAnchors, including dependencies, commands, permissions and incompatible
plugins on [our wiki](https://wiki.songoda.com/Epic_Anchors).
Feel free to also contribute to the wiki as a way to help others in the community with using the plugin.
## Support
If you encounter any issues while using the plugin, feel free to create a ticket
on [our support desk](https://support.songoda.com).
## Suggestions
For suggestions about features you think should be added to the plugin to increase its functionality, feel free to
create a thread over on [our feedback site](https://feedback.songoda.com).

180
pom.xml
View File

@ -1,119 +1,165 @@
<project xmlns="http://maven.apache.org/POM/4.0.0">
<?xml version="1.0" encoding="UTF-8"?>
<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.songoda</groupId>
<artifactId>EpicAnchors</artifactId>
<modelVersion>4.0.0</modelVersion>
<version>1.4.10c</version>
<version>2.0.0-ALPHA</version>
<!-- Run 'mvn versions:set -DgenerateBackupPoms=false -DnewVersion=X.Y.Z-DEV' to update version recursively -->
<name>EpicAnchors</name>
<description>Allow your players to keep chunks loaded for a limited amount of time for a cost.</description>
<url>https://songoda.com/marketplace/product/31</url>
<properties>
<java.version>1.8</java.version>
<java.release>8</java.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.projectKey>songoda_EpicAnchors</sonar.projectKey>
<sonar.organization>songoda</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.moduleKey>${project.groupId}:${project.artifactId}</sonar.moduleKey>
</properties>
<issueManagement>
<url>https://support.songoda.com/servicedesk/customer/portal/3</url>
<system>Jira Service Desk</system>
</issueManagement>
<scm>
<url>https://github.com/songoda/EpicAnchors</url>
<connection>scm:git:git:github.com/songoda/EpicAnchors.git</connection>
</scm>
<build>
<defaultGoal>clean install</defaultGoal>
<finalName>EpicAnchors-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>1.5.3</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>replace</goal>
</goals>
</execution>
</executions>
<configuration>
<file>${project.build.directory}/classes/plugin.yml</file>
<replacements>
<replacement>
<token>maven-version-number</token>
<value>${project.version}</value>
</replacement>
</replacements>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<version>3.3.0-SNAPSHOT</version>
<executions>
<execution>
<id>shaded</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<finalName>${project.name}-${project.version}</finalName>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
<artifactSet>
<includes>
<include>com.songoda:SongodaCore</include>
</includes>
</artifactSet>
<filters>
<!-- This filter is primarily for anvil gui right now -->
<!--<filter>
<artifact>com.songoda:SongodaCore-NMS*</artifact>
<includes>
<include>**</include>
</includes>
</filter>-->
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.songoda.core</pattern>
<shadedPattern>${project.groupId}.epicanchors.core</shadedPattern>
<shadedPattern>com.songoda.epicanchors.core</shadedPattern>
</relocation>
<relocation>
<pattern>epicanchors.nms</pattern>
<shadedPattern>com.songoda.epicanchors.nms</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/**</exclude>
<exclude>LICENSE</exclude>
<exclude>LICENSE.**</exclude>
</excludes>
</filter>
<filter>
<artifact>org.jetbrains:annotations</artifact>
<excludes>
<exclude>**</exclude>
</excludes>
</filter>
<filter>
<artifact>com.songoda:epicanchors-v*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<pluginRepositories>
<pluginRepository>
<id>apache.snapshots</id>
<url>https://repository.apache.org/snapshots/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>public</id>
<url>https://repo.songoda.com/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>songoda-public</id>
<url>https://repo.songoda.com/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.16.4</version>
<artifactId>spigot-api</artifactId>
<version>1.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.songoda</groupId>
<artifactId>SongodaCore</artifactId>
<version>LATEST</version>
<version>2.5.8</version>
<scope>compile</scope>
</dependency>
<!-- Dev dependencies -->
<dependency>
<groupId>com.songoda</groupId>
<artifactId>epicspawners</artifactId>
<version>6-pre4</version>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>21.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,128 @@
package com.songoda.epicanchors;
import com.songoda.epicanchors.utils.WorldUtils;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
public class Anchor {
private final int dbId;
private final UUID owner;
private final Location location;
private int ticksLeft;
public Anchor(int dbId, @Nullable UUID owner, @NotNull Location location, int ticksLeft) {
if (dbId <= 0) throw new IllegalArgumentException("Invalid value for dbId");
if (ticksLeft <= 0 && ticksLeft != -1) throw new IllegalArgumentException("Invalid value for ticksLeft");
Objects.requireNonNull(location.getWorld()); // Sanity check
this.dbId = dbId;
this.owner = owner;
this.location = location;
this.ticksLeft = ticksLeft;
}
/**
* <b></b>This method is automatically synchronized with the server's main thread using
* {@link org.bukkit.scheduler.BukkitScheduler#runTask(Plugin, Runnable)}</b>
*
* @see Bukkit#isPrimaryThread()
* @see org.bukkit.scheduler.BukkitScheduler#runTask(Plugin, Runnable)
*/
protected void init(Plugin plugin) {
if (Bukkit.isPrimaryThread()) {
WorldUtils.loadAnchoredChunk(getChunk(), plugin);
} else {
Bukkit.getScheduler().runTask(plugin, () -> init(plugin));
}
}
/**
* <b></b>This method is automatically synchronized with the server's main thread using
* {@link org.bukkit.scheduler.BukkitScheduler#runTask(Plugin, Runnable)}</b>
*
* @see Bukkit#isPrimaryThread()
* @see org.bukkit.scheduler.BukkitScheduler#runTask(Plugin, Runnable)
*/
protected void deInit(Plugin plugin) {
// TODO: Document that holograms are not removed or add boolean flag to remove them
if (Bukkit.isPrimaryThread()) {
WorldUtils.unloadAnchoredChunk(getChunk(), plugin);
} else {
Bukkit.getScheduler().runTask(plugin, () -> deInit(plugin));
}
}
public int getDbId() {
return this.dbId;
}
public UUID getOwner() {
return this.owner;
}
public boolean isLegacy() {
return this.owner == null;
}
public @NotNull Location getLocation() {
return this.location.clone();
}
public @NotNull World getWorld() {
return this.location.getWorld();
}
public @NotNull Chunk getChunk() {
return this.location.getChunk();
}
public int getTicksLeft() {
return this.ticksLeft;
}
@SuppressWarnings("unused")
public void setTicksLeft(int ticksLeft) {
if (ticksLeft < 0) throw new IllegalArgumentException("Invalid value for ticksLeft");
this.ticksLeft = ticksLeft;
}
@SuppressWarnings("UnusedReturnValue")
public int addTicksLeft(int ticks) {
if (!isInfinite()) {
this.ticksLeft += ticks;
}
return this.ticksLeft;
}
public int removeTicksLeft(int ticks) {
if (!isInfinite()) {
this.ticksLeft -= ticks;
if (this.ticksLeft < 0) {
this.ticksLeft = 0;
}
}
return this.ticksLeft;
}
public boolean isInfinite() {
return this.ticksLeft == -1;
}
}

View File

@ -0,0 +1,471 @@
package com.songoda.epicanchors;
import com.songoda.core.SongodaPlugin;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.compatibility.CompatibleSound;
import com.songoda.core.hooks.HologramManager;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTItem;
import com.songoda.core.utils.TextUtils;
import com.songoda.core.utils.TimeUtils;
import com.songoda.epicanchors.api.AnchorAccessCheck;
import com.songoda.epicanchors.files.DataManager;
import com.songoda.epicanchors.files.Settings;
import com.songoda.epicanchors.utils.Callback;
import com.songoda.epicanchors.utils.UpdateCallback;
import com.songoda.epicanchors.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class AnchorManager {
private static final String ERR_WORLD_NOT_READY = "EpicAnchors has not finished initializing that world yet";
private static final String NBT_TICKS_KEY = "EpicAnchors_Ticks".toLowerCase();
private final SongodaPlugin plugin;
private final DataManager dataManager;
private final Map<World, Set<Anchor>> anchors = new HashMap<>(3);
private final Set<Player> visualizedChunk = new HashSet<>();
private final List<AnchorAccessCheck> accessChecks = new LinkedList<>();
private boolean ready;
public AnchorManager(SongodaPlugin plugin, DataManager dataManager) {
this.plugin = Objects.requireNonNull(plugin);
this.dataManager = Objects.requireNonNull(dataManager);
}
protected void saveAll() {
for (Set<Anchor> anchorSet : anchors.values()) {
this.dataManager.updateAnchors(anchorSet, null);
}
}
protected void deInitAll() {
for (World world : anchors.keySet().toArray(new World[0])) {
deInitAnchors(world);
}
}
protected void initAnchorsAsync(@NotNull World world, @Nullable UpdateCallback callback) {
if (this.anchors.containsKey(world)) {
if (callback != null) {
callback.accept(null);
}
return;
}
long start = System.nanoTime();
this.dataManager.getAnchors(world, (ex, result) -> {
if (ex == null) {
this.anchors.computeIfAbsent(world, k -> new HashSet<>());
for (Anchor anchor : result) {
anchor.init(this.plugin);
this.anchors.computeIfAbsent(anchor.getWorld(), k -> new HashSet<>())
.add(anchor);
}
long end = System.nanoTime();
this.plugin.getLogger().info("Initialized anchors in world '" + world.getName() + "' " +
"(" + TimeUnit.NANOSECONDS.toMillis(end - start) + "ms)");
if (callback != null) {
callback.accept(null);
}
} else {
if (callback != null) {
callback.accept(ex);
} else {
Utils.logException(this.plugin, ex, "SQLite");
}
}
});
}
protected void deInitAnchors(@NotNull World world) {
Set<Anchor> tmpAnchors = this.anchors.remove(world);
if (tmpAnchors != null) {
this.dataManager.updateAnchors(tmpAnchors, null);
for (Anchor anchor : tmpAnchors) {
anchor.deInit(this.plugin);
}
}
}
protected void setReady() {
this.ready = true;
Bukkit.getScheduler().runTaskTimer(plugin, this::saveAll, 20L * 60 * 5, 20L * 60 * 5);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isReady(World world) {
return this.ready && anchors.containsKey(world);
}
/* Getter */
public Anchor[] getAnchors(@NotNull World world) {
Set<Anchor> set = anchors.get(world);
if (set != null) {
return set.toArray(new Anchor[0]);
}
return new Anchor[0];
}
public @Nullable Anchor getAnchor(@NotNull Block b) {
if (!isReady(b.getWorld())) {
throw new IllegalStateException(ERR_WORLD_NOT_READY);
}
Location bLoc = b.getLocation();
Set<Anchor> set = anchors.get(b.getWorld());
if (set != null) {
for (Anchor anchor : set) {
if (anchor.getLocation().equals(bLoc)) {
return anchor;
}
}
}
return null;
}
public boolean isAnchor(@NotNull Block b) {
return getAnchor(b) != null;
}
public boolean hasAnchor(@NotNull Chunk chunk) {
if (!isReady(chunk.getWorld())) {
throw new IllegalStateException(ERR_WORLD_NOT_READY);
}
Set<Anchor> set = anchors.get(chunk.getWorld());
if (set != null) {
for (Anchor anchor : set) {
if (anchor.getChunk().equals(chunk)) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unused")
public List<Anchor> searchAnchors(Location center, double searchRadius) {
return searchAnchors(center, searchRadius, false);
}
public List<Anchor> searchAnchors(Location center, double searchRadius, boolean ignoreHeight) {
List<Anchor> result = new ArrayList<>();
if (ignoreHeight) {
center = center.clone();
center.setY(0);
}
for (Anchor anchor : getAnchors(center.getWorld())) {
Location loc = anchor.getLocation();
if (ignoreHeight) {
loc.setY(0);
}
if (center.distance(loc) <= searchRadius) {
result.add(anchor);
}
}
return result;
}
/* Create 'n Destroy */
/**
* Creates a new anchor at a given location
*
* @param loc The block location for the anchor
* @param ticks The amount of ticks the anchor lives or -1 for infinite
*/
public void createAnchor(@NotNull Location loc, @NotNull UUID owner, int ticks, @Nullable Callback<Anchor> callback) {
if (!isReady(loc.getWorld())) {
throw new IllegalStateException(ERR_WORLD_NOT_READY);
}
this.dataManager.insertAnchorAsync(loc, Objects.requireNonNull(owner), ticks, (ex, anchor) -> {
if (ex != null) {
if (callback != null) {
callback.accept(ex, null);
} else {
Utils.logException(this.plugin, ex, "SQLite");
}
} else {
Bukkit.getScheduler().runTask(this.plugin, () -> {
Block b = loc.getBlock();
b.setType(Settings.MATERIAL.getMaterial().getMaterial());
anchors.computeIfAbsent(anchor.getWorld(), k -> new HashSet<>())
.add(anchor);
updateHologram(anchor);
if (callback != null) {
callback.accept(null, anchor);
}
});
}
});
}
public void destroyAnchor(@NotNull Anchor anchor) {
destroyAnchor(anchor, false);
}
public void destroyAnchor(@NotNull Anchor anchor, boolean forceSkipItemDrop) {
if (!isReady(anchor.getWorld())) {
throw new IllegalStateException(ERR_WORLD_NOT_READY);
}
for (Set<Anchor> value : anchors.values()) {
value.remove(anchor);
}
removeHologram(anchor);
Location anchorLoc = anchor.getLocation();
Block anchorBlock = anchorLoc.getBlock();
Material anchorMaterial = anchorBlock.getType();
if (anchorBlock.getType() == Settings.MATERIAL.getMaterial().getMaterial()) {
anchorBlock.setType(Material.AIR);
}
// Drop anchor as an item
if (!forceSkipItemDrop &&
Settings.ALLOW_ANCHOR_BREAKING.getBoolean() &&
(anchor.isInfinite() || anchor.getTicksLeft() >= 20)) {
anchor.getWorld().dropItemNaturally(anchorLoc, createAnchorItem(anchor.getTicksLeft(), anchorMaterial));
}
// Particles & Sound
anchor.getWorld().playSound(anchorLoc, CompatibleSound.ENTITY_GENERIC_EXPLODE.getSound(), 10, 10);
CompatibleParticleHandler.spawnParticles(CompatibleParticleHandler.ParticleType.getParticle(Settings.PARTICLE_DESTROY.getString()),
anchor.getLocation().add(.5, .5, .5), 100, .5, .5, .5);
anchor.deInit(this.plugin);
this.dataManager.deleteAnchorAsync(anchor);
}
/* Anchor access */
@SuppressWarnings("unused")
public void registerAccessCheck(AnchorAccessCheck accessCheck) {
if (!accessChecks.contains(accessCheck)) {
// Adding at the start of the list makes sure the default check is
accessChecks.add(accessCheck);
}
}
/**
* @param accessCheck The {@link AnchorAccessCheck} to remove
*
* @return true if the {@link AnchorAccessCheck} has been found and removed, false otherwise
*/
@SuppressWarnings("unused")
public boolean unregisterAccessCheck(AnchorAccessCheck accessCheck) {
return accessChecks.remove(accessCheck);
}
public boolean hasAccess(@NotNull Anchor anchor, @NotNull OfflinePlayer p) {
return hasAccess(anchor, p.getUniqueId());
}
/**
* Checks if a player has access to an Anchor. By default only the owner has access to an Anchor.
* <br>
* Other plugins can grant access to other players (e.g. friends).
* <br>
* Legacy anchors without an owner automatically grant access to all players.
*
* @return true if the player may access the Anchor, false otherwise
*
* @see #registerAccessCheck(AnchorAccessCheck)
*/
public boolean hasAccess(@NotNull Anchor anchor, @NotNull UUID uuid) {
if (anchor.isLegacy() || anchor.getOwner().equals(uuid)) return true;
for (AnchorAccessCheck accessCheck : this.accessChecks) {
if (accessCheck.check(anchor, uuid)) {
return true;
}
}
return false;
}
/* Anchor item */
public ItemStack createAnchorItem(int ticks) {
return createAnchorItem(ticks, Settings.MATERIAL.getMaterial());
}
public ItemStack createAnchorItem(int ticks, Material material) {
return createAnchorItem(ticks, CompatibleMaterial.getMaterial(material));
}
public ItemStack createAnchorItem(int ticks, CompatibleMaterial material) {
if (ticks <= 0 && ticks != -1) throw new IllegalArgumentException();
ItemStack item = material.getItem();
ItemMeta meta = item.getItemMeta();
assert meta != null;
meta.setDisplayName(formatAnchorText(ticks, false));
meta.setLore(TextUtils.formatText(Settings.LORE.getString().split("\r?\n")));
item.setItemMeta(meta);
NBTItem nbtItem = NmsManager.getNbt().of(item);
nbtItem.set(NBT_TICKS_KEY, ticks);
return nbtItem.finish();
}
public static int getTicksFromItem(ItemStack item) {
NBTItem nbtItem = NmsManager.getNbt().of(item);
if (nbtItem.has(NBT_TICKS_KEY)) {
return nbtItem.getInt(NBT_TICKS_KEY);
}
// Legacy code (pre v2) to stay cross-version compatible
if (Settings.MATERIAL.getMaterial().getMaterial() == item.getType()) {
if (nbtItem.has("ticks")) {
int result = nbtItem.getInt("ticks");
return result == -99 ? -1 : result;
}
// Tries to get the ticks remaining from hidden text
if (item.hasItemMeta() &&
item.getItemMeta().hasDisplayName() &&
item.getItemMeta().getDisplayName().contains(":")) {
try {
int result = Integer.parseInt(item.getItemMeta().getDisplayName().replace("§", "").split(":")[0]);
return result == -99 ? -1 : result;
} catch (NumberFormatException ignore) {
}
}
}
return 0;
}
/* Chunk visualization */
public boolean toggleChunkVisualized(Player p) {
boolean visualize = !hasChunksVisualized(p);
setChunksVisualized(p, visualize);
return visualize;
}
public void setChunksVisualized(Player p, boolean visualize) {
if (visualize) {
this.visualizedChunk.add(p);
} else {
this.visualizedChunk.remove(p);
}
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean hasChunksVisualized(Player p) {
return this.visualizedChunk.contains(p);
}
/* Holograms */
public void updateHolograms(List<Anchor> anchors) {
// are holograms enabled?
if (!Settings.HOLOGRAMS.getBoolean() || !HologramManager.getManager().isEnabled()) return;
Map<Location, List<String>> hologramData = new HashMap<>(anchors.size());
for (Anchor anchor : anchors) {
hologramData.put(anchor.getLocation(),
Collections.singletonList(formatAnchorText(anchor.getTicksLeft(), true)));
}
// Create the holograms
HologramManager.bulkUpdateHolograms(hologramData);
}
private void updateHologram(Anchor anchor) {
updateHolograms(Collections.singletonList(anchor));
}
private String formatAnchorText(int ticks, boolean shorten) {
String remaining;
if (ticks < 0) {
remaining = this.plugin.getLocale().getMessage("Infinite").getMessage();
} else {
long millis = (ticks / 20L) * 1000L;
remaining = TimeUtils.makeReadable(millis);
if (shorten && millis > 60 * 5 * 1000 /* 5 minutes */ &&
remaining.charAt(remaining.length() - 1) == 's') {
int i = remaining.lastIndexOf(' ');
remaining = remaining.substring(0, i);
}
if (remaining.isEmpty()) {
remaining = "0s";
}
}
return TextUtils.formatText(Settings.NAME_TAG.getString().replace("{REMAINING}", remaining));
}
private static void removeHologram(Anchor anchor) {
HologramManager.removeHologram(anchor.getLocation());
}
}

View File

@ -4,214 +4,150 @@ import com.songoda.core.SongodaCore;
import com.songoda.core.SongodaPlugin;
import com.songoda.core.commands.CommandManager;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.configuration.Config;
import com.songoda.core.database.DatabaseConnector;
import com.songoda.core.database.SQLiteConnector;
import com.songoda.core.gui.GuiManager;
import com.songoda.core.hooks.EconomyManager;
import com.songoda.core.hooks.HologramManager;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTItem;
import com.songoda.core.utils.TextUtils;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.anchor.AnchorManager;
import com.songoda.epicanchors.commands.*;
import com.songoda.epicanchors.listeners.BlockListeners;
import com.songoda.epicanchors.listeners.InteractListeners;
import com.songoda.epicanchors.listeners.PortalListeners;
import com.songoda.epicanchors.settings.Settings;
import com.songoda.epicanchors.commands.EpicAnchorsCommand;
import com.songoda.epicanchors.commands.sub.GiveCommand;
import com.songoda.epicanchors.commands.sub.ReloadCommand;
import com.songoda.epicanchors.commands.sub.SettingsCommand;
import com.songoda.epicanchors.commands.sub.ShowCommand;
import com.songoda.epicanchors.files.DataManager;
import com.songoda.epicanchors.files.Settings;
import com.songoda.epicanchors.files.migration.AnchorMigration;
import com.songoda.epicanchors.files.migration._1_InitialMigration;
import com.songoda.epicanchors.listener.AnchorListener;
import com.songoda.epicanchors.listener.BlockListener;
import com.songoda.epicanchors.listener.WorldListener;
import com.songoda.epicanchors.tasks.AnchorTask;
import com.songoda.epicanchors.tasks.VisualizeTask;
import com.songoda.epicanchors.utils.Methods;
import org.apache.commons.lang.math.NumberUtils;
import com.songoda.epicanchors.utils.ThreadSync;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.World;
import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
public class EpicAnchors extends SongodaPlugin {
private static EpicAnchors INSTANCE;
private final Config dataFile = new Config(this, "data.yml");
private final GuiManager guiManager = new GuiManager(this);
public final class EpicAnchors extends SongodaPlugin {
private GuiManager guiManager;
private AnchorManager anchorManager;
private CommandManager commandManager;
public static EpicAnchors getInstance() {
return INSTANCE;
}
private DataManager dataManager;
@Override
public void onPluginLoad() {
INSTANCE = this;
}
@Override
public void onPluginEnable() {
// Songoda Updater
SongodaCore.registerPlugin(this, 31, CompatibleMaterial.END_PORTAL_FRAME);
// Initialize database
this.getLogger().info("Initializing SQLite...");
DatabaseConnector dbCon = new SQLiteConnector(this);
this.dataManager = new DataManager(dbCon, this);
AnchorMigration anchorMigration = new AnchorMigration(dbCon, this.dataManager,
new _1_InitialMigration());
anchorMigration.runMigrations();
anchorMigration.migrateLegacyData(this);
this.anchorManager = new AnchorManager(this, this.dataManager);
// Economy [1/2]
EconomyManager.load();
// Config
Settings.setupConfig();
this.setLocale(Settings.LANGUAGE.getString(), false);
// Economy [2/2]
EconomyManager.getManager().setPreferredHook(Settings.ECONOMY_PLUGIN.getString());
// Holograms
HologramManager.load(this);
// Event Listener
this.guiManager = new GuiManager(this);
guiManager.init();
PluginManager pluginManager = Bukkit.getPluginManager();
pluginManager.registerEvents(new WorldListener(
world -> this.anchorManager.initAnchorsAsync(world, null),
world -> this.anchorManager.deInitAnchors(world)),
this);
pluginManager.registerEvents(new AnchorListener(this), this);
pluginManager.registerEvents(new BlockListener(this.anchorManager), this);
// Commands
CommandManager commandManager = new CommandManager(this);
commandManager.addCommand(new EpicAnchorsCommand(this, commandManager))
.addSubCommands(
new GiveCommand(this),
new ReloadCommand(this),
new SettingsCommand(this, this.guiManager),
new ShowCommand(this)
);
}
@Override
public void onPluginDisable() {
saveToFile();
// Save all Anchors
if (this.dataManager != null) {
this.anchorManager.deInitAll();
this.dataManager.close();
}
// Remove all holograms
HologramManager.removeAllHolograms();
}
@Override
public void onDataLoad() {}
public void onDataLoad() {
new Thread(() -> {
ThreadSync tSync = new ThreadSync();
@Override
public void onPluginEnable() {
// Run Songoda Updater
SongodaCore.registerPlugin(this, 31, CompatibleMaterial.END_PORTAL_FRAME);
for (World w : Bukkit.getWorlds()) {
this.anchorManager.initAnchorsAsync(w, (ex) -> {
if (ex != null) {
this.getLogger().log(Level.FINER, ex, () -> "Failed to initialize world '" + w.getName() + "'");
}
// Load Economy
EconomyManager.load();
tSync.release();
});
// Setup Config
Settings.setupConfig();
this.setLocale(Settings.LANGUGE_MODE.getString(), false);
tSync.waitForRelease();
tSync.reset();
}
// Set economy preference
EconomyManager.getManager().setPreferredHook(Settings.ECONOMY_PLUGIN.getString());
this.anchorManager.setReady();
// Register commands
this.commandManager = new CommandManager(this);
this.commandManager.addCommand(new CommandEpicAnchors(this))
.addSubCommands(
new CommandGive(this),
new CommandReload(this),
new CommandSettings(this, guiManager),
new CommandShow(this)
);
anchorManager = new AnchorManager();
Bukkit.getScheduler().runTaskLater(this, this::loadAnchorsFromFile, 5L);
// Start tasks
new AnchorTask(this);
new VisualizeTask(this);
// Register Listeners
guiManager.init();
PluginManager pluginManager = Bukkit.getPluginManager();
pluginManager.registerEvents(new BlockListeners(this), this);
pluginManager.registerEvents(new InteractListeners(this), this);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9))
pluginManager.registerEvents(new PortalListeners(this), this);
// Register Hologram Plugin
HologramManager.load(this);
if (Settings.HOLOGRAMS.getBoolean())
loadHolograms();
Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::saveToFile, 6000, 6000);
// Start tasks
new AnchorTask(this).startTask();
new VisualizeTask(this).startTask();
}).start();
}
@Override
public void onConfigReload() {
this.setLocale(Settings.LANGUGE_MODE.getString(), true);
this.loadAnchorsFromFile();
this.setLocale(Settings.LANGUAGE.getString(), true);
}
@Override
public List<Config> getExtraConfig() {
return null;
}
void loadHolograms() {
Collection<Anchor> anchors = getAnchorManager().getAnchors().values();
if (anchors.size() == 0) return;
for (Anchor anchor : anchors) {
if (anchor.getWorld() == null) continue;
updateHologram(anchor);
}
}
public void clearHologram(Anchor anchor) {
HologramManager.removeHologram(correctHeight(anchor.getLocation()));
}
public void updateHologram(Anchor anchor) {
// are holograms enabled?
if (!Settings.HOLOGRAMS.getBoolean() || !HologramManager.getManager().isEnabled()) return;
// verify that this is a anchor
if (anchor.getLocation().getBlock().getType() != Settings.MATERIAL.getMaterial().getMaterial()) return;
// grab the name
String name = Methods.formatName(anchor.getTicksLeft()).trim();
Location location = correctHeight(anchor.getLocation());
// create the hologram
HologramManager.updateHologram(location, name);
}
private Location correctHeight(Location location) {
if (location.getBlock().getType() != CompatibleMaterial.END_PORTAL_FRAME.getMaterial())
location.add(0, .05, 0);
return location;
}
private void loadAnchorsFromFile() {
dataFile.load();
if (!dataFile.contains("Anchors")) return;
for (String locationStr : dataFile.getConfigurationSection("Anchors").getKeys(false)) {
Location location = Methods.unserializeLocation(locationStr);
int ticksLeft = dataFile.getInt("Anchors." + locationStr + ".ticksLeft");
anchorManager.addAnchor(location, new Anchor(location, ticksLeft));
}
}
private void saveToFile() {
dataFile.clearConfig(true);
for (Anchor anchor : anchorManager.getAnchors().values()) {
String locationStr = Methods.serializeLocation(anchor.getLocation());
dataFile.set("Anchors." + locationStr + ".ticksLeft", anchor.getTicksLeft());
}
dataFile.save();
}
public int getTicksFromItem(ItemStack item) {
NBTItem nbtItem = NmsManager.getNbt().of(item);
if (nbtItem.has("ticks")) {
return nbtItem.getNBTObject("ticks").asInt();
}
// Legacy code. Tries to get the ticks remaining from hidden text.
if (!item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return 0;
if (item.getItemMeta().getDisplayName().contains(":")) {
return Integer.parseInt(item.getItemMeta().getDisplayName().replace("\u00A7", "").split(":")[0]);
}
return 0;
}
public ItemStack makeAnchorItem(int ticks) {
ItemStack item = Settings.MATERIAL.getMaterial().getItem();
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(Methods.formatName(ticks));
ArrayList<String> lore = new ArrayList<>();
String[] parts = Settings.LORE.getString().split("\\|");
for (String line : parts) {
lore.add(TextUtils.formatText(line));
}
meta.setLore(lore);
item.setItemMeta(meta);
NBTItem nbtItem = NmsManager.getNbt().of(item);
nbtItem.set("ticks", ticks);
return nbtItem.finish();
}
public CommandManager getCommandManager() {
return commandManager;
return Collections.emptyList();
}
public GuiManager getGuiManager() {
return guiManager;
return this.guiManager;
}
public AnchorManager getAnchorManager() {
return anchorManager;
return this.anchorManager;
}
}

View File

@ -1,110 +0,0 @@
package com.songoda.epicanchors.anchor;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.hooks.EconomyManager;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.settings.Settings;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class Anchor {
private Location location;
private int ticksLeft;
private boolean isInfinite;
private final int chunkX;
private final int chunkZ;
public Anchor(Location location, int ticksLeft) {
this.location = location;
this.chunkX = location.getBlockX() >> 4;
this.chunkZ = location.getBlockZ() >> 4;
this.ticksLeft = ticksLeft;
this.isInfinite = (ticksLeft == -99);
}
public void addTime(String type, Player player) {
EpicAnchors instance = EpicAnchors.getInstance();
if (type.equals("ECO")) {
if (!EconomyManager.isEnabled()) return;
double cost = instance.getConfig().getDouble("Main.Economy Cost");
if (EconomyManager.hasBalance(player, cost)) {
EconomyManager.withdrawBalance(player, cost);
} else {
instance.getLocale().getMessage("event.upgrade.cannotafford").sendPrefixedMessage(player);
return;
}
} else if (type.equals("XP")) {
int cost = instance.getConfig().getInt("Main.XP Cost");
if (player.getLevel() >= cost || player.getGameMode() == GameMode.CREATIVE) {
if (player.getGameMode() != GameMode.CREATIVE) {
player.setLevel(player.getLevel() - cost);
}
} else {
instance.getLocale().getMessage("event.upgrade.cannotafford").sendPrefixedMessage(player);
return;
}
}
ticksLeft = ticksLeft + 20 * 60 * 30;
Sound sound = ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9) ? Sound.ENTITY_PLAYER_LEVELUP : Sound.valueOf("LEVEL_UP");
player.playSound(player.getLocation(), sound, 0.6F, 15.0F);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9))
player.getWorld().spawnParticle(Particle.SPELL_WITCH, getLocation().add(.5, .5, .5), 100, .5, .5, .5);
}
public void bust() {
EpicAnchors plugin = EpicAnchors.getInstance();
if (Settings.ALLOW_ANCHOR_BREAKING.getBoolean() && getTicksLeft() > 0) {
ItemStack item = plugin.makeAnchorItem(getTicksLeft());
getLocation().getWorld().dropItemNaturally(getLocation(), item);
}
plugin.clearHologram(this);
location.getBlock().setType(Material.AIR);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9))
location.getWorld().spawnParticle(Particle.LAVA, location.clone().add(.5, .5, .5), 5, 0, 0, 0, 5);
location.getWorld().playSound(location, ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)
? Sound.ENTITY_GENERIC_EXPLODE : Sound.valueOf("EXPLODE"), 10, 10);
plugin.getAnchorManager().removeAnchor(location);
}
public Location getLocation() {
return location.clone();
}
public int getChunkX() {
return chunkX;
}
public int getChunkZ() {
return chunkZ;
}
public World getWorld() {
return location.getWorld();
}
public int getTicksLeft() {
return ticksLeft;
}
public void setTicksLeft(int ticksLeft) {
this.ticksLeft = ticksLeft;
}
public boolean isInfinite() {
return isInfinite;
}
public void setInfinite(boolean infinite) {
isInfinite = infinite;
}
}

View File

@ -1,45 +0,0 @@
package com.songoda.epicanchors.anchor;
import org.bukkit.Location;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class AnchorManager {
private final Map<Location, Anchor> registeredAnchors = new HashMap<>();
public Anchor addAnchor(Location location, Anchor anchor) {
return registeredAnchors.put(roundLocation(location), anchor);
}
public void removeAnchor(Location location) {
registeredAnchors.remove(roundLocation(location));
}
public Anchor getAnchor(Location location) {
return registeredAnchors.get(roundLocation(location));
}
public Anchor getAnchor(String world, int chunkX, int chunkZ) {
return this.registeredAnchors.values().stream()
.filter(anchor -> anchor.getWorld().getName().equals(world) && anchor.getChunkX() == chunkX && anchor.getChunkZ() == chunkZ).findFirst().orElse(null);
}
public boolean isAnchor(Location location) {
return registeredAnchors.containsKey(location);
}
public Map<Location, Anchor> getAnchors() {
return Collections.unmodifiableMap(registeredAnchors);
}
private Location roundLocation(org.bukkit.Location location) {
location = location.clone();
location.setX(location.getBlockX());
location.setY(location.getBlockY());
location.setZ(location.getBlockZ());
return location;
}
}

View File

@ -0,0 +1,10 @@
package com.songoda.epicanchors.api;
import com.songoda.epicanchors.Anchor;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public interface AnchorAccessCheck {
boolean check(@NotNull Anchor anchor, @NotNull UUID uuid);
}

View File

@ -1,90 +0,0 @@
package com.songoda.epicanchors.commands;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CommandGive extends AbstractCommand {
final EpicAnchors instance;
public CommandGive(EpicAnchors instance) {
super(false, "give");
this.instance = instance;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if (args.length != 2) return ReturnType.SYNTAX_ERROR;
Player target = Bukkit.getPlayer(args[0]);
if (target == null && !args[0].trim().toLowerCase().equals("all")) {
instance.getLocale().newMessage("&cThat is not a player...").sendPrefixedMessage(sender);
return ReturnType.SYNTAX_ERROR;
}
ItemStack itemStack;
if (Methods.isInt(args[1])) {
itemStack = (Integer.parseInt(args[1]) <= 0) ? instance.makeAnchorItem(-99) : instance.makeAnchorItem(Integer.parseInt(args[1]) * 20 * 60 * 60);
} else if (args[1].toLowerCase().equals("infinite")) {
itemStack = instance.makeAnchorItem(-99);
} else {
instance.getLocale().newMessage("&cYou can only use whole numbers...").sendPrefixedMessage(sender);
return ReturnType.FAILURE;
}
if (target != null) {
target.getInventory().addItem(itemStack);
instance.getLocale().getMessage("command.give.success").sendPrefixedMessage(target);
} else {
for (Player player : Bukkit.getOnlinePlayers()) {
player.getInventory().addItem(itemStack);
instance.getLocale().getMessage("command.give.success").sendPrefixedMessage(player);
}
}
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender commandSender, String... strings) {
if (strings.length == 1) {
List<String> players = new ArrayList<>();
players.add("all");
for (Player player : Bukkit.getOnlinePlayers()) {
players.add(player.getName());
}
return players;
} else if (strings.length == 2) {
return Arrays.asList("1", "2", "3", "4", "5");
}
return null;
}
@Override
public String getPermissionNode() {
return "epicanchors.admin";
}
@Override
public String getSyntax() {
return "/ea give <player/all> <amount in hours / infinite>";
}
@Override
public String getDescription() {
return "Gives an operator the ability to spawn a ChunkAnchor of his or her choice.";
}
}

View File

@ -1,30 +1,35 @@
package com.songoda.epicanchors.commands;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.core.commands.CommandManager;
import com.songoda.epicanchors.EpicAnchors;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import java.util.Collections;
import java.util.List;
public class CommandEpicAnchors extends AbstractCommand {
public class EpicAnchorsCommand extends AbstractCommand {
private final EpicAnchors plugin;
private final CommandManager commandManager;
final EpicAnchors instance;
public EpicAnchorsCommand(EpicAnchors plugin, CommandManager commandManager) {
super(CommandType.CONSOLE_OK, false, "EpicAnchors");
public CommandEpicAnchors(EpicAnchors instance) {
super(false, "EpicAnchors");
this.instance = instance;
this.plugin = plugin;
this.commandManager = commandManager;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
sender.sendMessage("");
instance.getLocale().newMessage("&7Version " + instance.getDescription().getVersion()
this.plugin.getLocale().newMessage("&7Version " + this.plugin.getDescription().getVersion()
+ " Created with <3 by &5&l&oSongoda").sendPrefixedMessage(sender);
for (AbstractCommand command : instance.getCommandManager().getAllCommands()) {
if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&8 - &a" + command.getSyntax() + "&7 - " + command.getDescription()));
for (AbstractCommand cmd : this.commandManager.getAllCommands()) {
if (cmd.getPermissionNode() == null || sender.hasPermission(cmd.getPermissionNode())) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
"&8 - &a" + cmd.getSyntax() + "&7 - " + cmd.getDescription()));
}
}
sender.sendMessage("");
@ -34,7 +39,7 @@ public class CommandEpicAnchors extends AbstractCommand {
@Override
protected List<String> onTab(CommandSender commandSender, String... strings) {
return null;
return Collections.emptyList();
}
@Override

View File

@ -0,0 +1,116 @@
package com.songoda.epicanchors.commands.sub;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class GiveCommand extends AbstractCommand {
private final EpicAnchors plugin;
public GiveCommand(EpicAnchors plugin) {
super(CommandType.CONSOLE_OK, false, "give");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if (args.length != 2) return ReturnType.SYNTAX_ERROR;
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null &&
!args[0].trim().equalsIgnoreCase("all") &&
!args[0].trim().equalsIgnoreCase("@a")) {
plugin.getLocale().newMessage("&cThat is not a player...").sendPrefixedMessage(sender);
return ReturnType.SYNTAX_ERROR;
}
ItemStack itemStack;
if (Utils.isInt(args[1]) && Integer.parseInt(args[1]) > 0) {
itemStack = plugin.getAnchorManager().createAnchorItem(Integer.parseInt(args[1]) * 20 * 60 * 60);
} else if (args[1].equalsIgnoreCase("infinite")) {
itemStack = plugin.getAnchorManager().createAnchorItem(-1);
} else {
plugin.getLocale().newMessage("&cYou can only use positive whole numbers...").sendPrefixedMessage(sender);
return ReturnType.FAILURE;
}
if (target != null) {
target.getInventory().addItem(itemStack);
plugin.getLocale().getMessage("command.give.success").sendPrefixedMessage(target);
} else {
for (Player online : Bukkit.getOnlinePlayers()) {
online.getInventory().addItem(itemStack);
plugin.getLocale().getMessage("command.give.success").sendPrefixedMessage(online);
}
}
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender commandSender, String... args) {
if (args.length == 1) {
Set<String> players = new HashSet<>();
for (Player player : Bukkit.getOnlinePlayers()) {
players.add(player.getName());
}
players.add("all");
if ("@a".startsWith(args[0].toLowerCase())) {
players.add("@a");
}
return Utils.getMatches(args[0], players, true);
} else if (args.length == 2) {
List<String> result = new ArrayList<>();
result.add("infinite");
if (args[1].isEmpty()) {
for (int i = 1; i <= 5; ++i) {
result.add(String.valueOf(i));
}
} else if (Utils.isInt(args[1]) && args[1].charAt(0) != '-') {
result.add(args[1] + "0");
result.add(args[1] + "00");
result.add(args[1] + "000");
}
return Utils.getMatches(args[1], result, true);
}
return Collections.emptyList();
}
@Override
public String getPermissionNode() {
return "EpicAnchors.cmd.give";
}
@Override
public String getSyntax() {
return "/ea give <player/all> <amount in hours / infinite>";
}
@Override
public String getDescription() {
return "Gives an operator the ability to spawn a ChunkAnchor of his or her choice.";
}
}

View File

@ -1,35 +1,36 @@
package com.songoda.epicanchors.commands;
package com.songoda.epicanchors.commands.sub;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.epicanchors.EpicAnchors;
import org.bukkit.command.CommandSender;
import java.util.Collections;
import java.util.List;
public class CommandReload extends AbstractCommand {
public class ReloadCommand extends AbstractCommand {
private final EpicAnchors plugin;
final EpicAnchors instance;
public CommandReload(EpicAnchors instance) {
super(false, "reload");
this.instance = instance;
public ReloadCommand(EpicAnchors plugin) {
super(CommandType.CONSOLE_OK, false, "reload");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
instance.reloadConfig();
instance.getLocale().getMessage("&7Configuration and Language files reloaded.").sendPrefixedMessage(sender);
plugin.reloadConfig();
plugin.getLocale().getMessage("&7Configuration and Language files reloaded.").sendPrefixedMessage(sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
return Collections.emptyList();
}
@Override
public String getPermissionNode() {
return "epicanchors.admin";
return "EpicAnchors.cmd.reload";
}
@Override
@ -41,5 +42,4 @@ public class CommandReload extends AbstractCommand {
public String getDescription() {
return "Reload the Configuration and Language files.";
}
}

View File

@ -1,4 +1,4 @@
package com.songoda.epicanchors.commands;
package com.songoda.epicanchors.commands.sub;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.core.configuration.editor.PluginConfigGui;
@ -7,15 +7,16 @@ import com.songoda.epicanchors.EpicAnchors;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
public class CommandSettings extends AbstractCommand {
public class SettingsCommand extends AbstractCommand {
private final EpicAnchors instance;
private final GuiManager guiManager;
final EpicAnchors instance;
final GuiManager guiManager;
public SettingsCommand(EpicAnchors instance, GuiManager manager) {
super(CommandType.PLAYER_ONLY, false, "settings");
public CommandSettings(EpicAnchors instance, GuiManager manager) {
super(true, "settings");
this.instance = instance;
this.guiManager = manager;
}
@ -23,17 +24,18 @@ public class CommandSettings extends AbstractCommand {
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
guiManager.showGUI((Player) sender, new PluginConfigGui(instance));
return AbstractCommand.ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender commandSender, String... strings) {
return null;
return Collections.emptyList();
}
@Override
public String getPermissionNode() {
return "epicanchors.admin";
return "EpicAnchors.cmd.settings";
}
@Override

View File

@ -1,50 +1,49 @@
package com.songoda.epicanchors.commands;
package com.songoda.epicanchors.commands.sub;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.tasks.VisualizeTask;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
public class CommandShow extends AbstractCommand {
public class ShowCommand extends AbstractCommand {
private final EpicAnchors plugin;
public CommandShow(EpicAnchors plugin) {
super(true, "show");
public ShowCommand(EpicAnchors plugin) {
super(CommandType.PLAYER_ONLY, false, "show");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if(!(sender instanceof Player)) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "Command must be called as a player");
return ReturnType.FAILURE;
}
Player player = (Player) sender;
if (args.length != 0)
return ReturnType.SYNTAX_ERROR;
if (args.length != 0) return ReturnType.SYNTAX_ERROR;
if(VisualizeTask.togglePlayer(player))
plugin.getLocale().getMessage("command.show.start").sendPrefixedMessage(player);
else
plugin.getLocale().getMessage("command.show.stop").sendPrefixedMessage(player);
boolean visualize = this.plugin.getAnchorManager().toggleChunkVisualized((Player) sender);
plugin.getLocale().getMessage("command.show." + (visualize ? "start" : "stop"))
.sendPrefixedMessage(sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
return Collections.emptyList();
}
@Override
public String getPermissionNode() {
return "epicanchors.show";
return "EpicAnchors.cmd.show";
}
@Override

View File

@ -0,0 +1,274 @@
package com.songoda.epicanchors.files;
import com.songoda.core.database.DataManagerAbstract;
import com.songoda.core.database.DatabaseConnector;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.files.migration.AnchorMigration;
import com.songoda.epicanchors.utils.Callback;
import com.songoda.epicanchors.utils.UpdateCallback;
import com.songoda.epicanchors.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DataManager extends DataManagerAbstract {
private final ExecutorService thread = Executors.newSingleThreadExecutor();
private final String anchorTable;
public DataManager(DatabaseConnector databaseConnector, Plugin plugin) {
super(databaseConnector, plugin);
this.anchorTable = getTableName(super.getTablePrefix(), "anchors");
}
public void close() {
if (!this.thread.isShutdown()) {
this.thread.shutdown();
try {
if (!this.thread.awaitTermination(60, TimeUnit.SECONDS)) {
// Try stopping the thread forcefully (there is basically no hope left for the data)
this.thread.shutdownNow();
}
} catch (InterruptedException ex) {
Utils.logException(super.plugin, ex);
}
this.databaseConnector.closeConnection();
}
}
public void exists(@NotNull String worldName, int x, int y, int z, @NotNull Callback<Boolean> callback) {
this.databaseConnector.connect((con) -> {
try (PreparedStatement ps = con.prepareStatement("SELECT id FROM " + this.anchorTable +
" WHERE world_name =? AND x =? AND y =? AND z=?;")) {
ps.setString(1, worldName);
ps.setInt(2, x);
ps.setInt(3, y);
ps.setInt(4, z);
ResultSet rs = ps.executeQuery();
callback.accept(null, rs.next());
} catch (Exception ex) {
resolveCallback(callback, ex);
}
});
}
public void getAnchors(@Nullable World world, @NotNull Callback<List<Anchor>> callback) {
List<Anchor> result = new ArrayList<>();
this.databaseConnector.connect((con) -> {
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM " + this.anchorTable +
(world != null ? " WHERE world_name =?" : "") + ";")) {
if (world != null) {
ps.setString(1, world.getName());
}
ResultSet rs = ps.executeQuery();
while (rs.next()) {
result.add(extractAnchor(rs));
}
callback.accept(null, result);
} catch (Exception ex) {
resolveCallback(callback, ex);
}
});
}
public void insertAnchorAsync(Location loc, UUID owner, int ticks, Callback<Anchor> callback) {
this.thread.execute(() -> insertAnchor(loc, owner, ticks, callback));
}
public void insertAnchor(Location loc, UUID owner, int ticks, Callback<Anchor> callback) {
this.databaseConnector.connect((con) -> {
try (PreparedStatement ps = con.prepareStatement("INSERT INTO " + this.anchorTable +
"(owner, world_name,x,y,z, ticks_left) VALUES (?,?,?,?,?, ?);");// Future SQLite version might support 'RETURNING *'
PreparedStatement psFetch = con.prepareStatement("SELECT * FROM " + this.anchorTable +
" WHERE world_name =? AND x =? AND y =? AND z=?;")) {
ps.setString(1, owner != null ? owner.toString() : null);
ps.setString(2, Objects.requireNonNull(loc.getWorld()).getName());
psFetch.setString(1, Objects.requireNonNull(loc.getWorld()).getName());
ps.setInt(3, loc.getBlockX());
psFetch.setInt(2, loc.getBlockX());
ps.setInt(4, loc.getBlockY());
psFetch.setInt(3, loc.getBlockY());
ps.setInt(5, loc.getBlockZ());
psFetch.setInt(4, loc.getBlockZ());
ps.setInt(6, ticks);
ps.executeUpdate();
if (callback != null) {
ResultSet rs = psFetch.executeQuery();
rs.next();
callback.accept(null, extractAnchor(rs));
}
} catch (Exception ex) {
resolveCallback(callback, ex);
}
});
}
public void migrateAnchor(List<AnchorMigration.LegacyAnchorEntry> anchorEntries, UpdateCallback callback) {
this.databaseConnector.connect((con) -> {
con.setAutoCommit(false);
SQLException err = null;
try (PreparedStatement ps = con.prepareStatement("INSERT INTO " + this.anchorTable +
"(world_name,x,y,z, ticks_left) VALUES (?,?,?,?, ?);")) {
for (AnchorMigration.LegacyAnchorEntry entry : anchorEntries) {
ps.setString(1, entry.worldName);
ps.setInt(2, entry.x);
ps.setInt(3, entry.y);
ps.setInt(4, entry.z);
ps.setInt(5, entry.ticksLeft);
ps.addBatch();
}
int[] batchRes = ps.executeBatch();
for (int i : batchRes) {
if (i < 0 && i != Statement.SUCCESS_NO_INFO) {
throw new AssertionError("Batch-INSERT failed for at least one statement with code " + i + "");
}
}
} catch (SQLException ex) {
err = ex;
}
if (err == null) {
con.commit();
resolveUpdateCallback(callback, null);
} else {
con.rollback();
resolveUpdateCallback(callback, err);
}
con.setAutoCommit(true);
});
}
public void updateAnchors(Collection<Anchor> anchors, UpdateCallback callback) {
this.databaseConnector.connect((con) -> {
con.setAutoCommit(false);
SQLException err = null;
for (Anchor anchor : anchors) {
try (PreparedStatement ps = con.prepareStatement("UPDATE " + this.anchorTable +
" SET ticks_left =? WHERE id =?;")) {
ps.setInt(1, anchor.getTicksLeft());
ps.setInt(2, anchor.getDbId());
ps.executeUpdate();
} catch (SQLException ex) {
err = ex;
break;
}
}
if (err == null) {
con.commit();
resolveUpdateCallback(callback, null);
} else {
con.rollback();
resolveUpdateCallback(callback, err);
}
con.setAutoCommit(true);
});
}
public void deleteAnchorAsync(Anchor anchor) {
deleteAnchorAsync(anchor, null);
}
public void deleteAnchorAsync(Anchor anchor, UpdateCallback callback) {
this.thread.execute(() ->
this.databaseConnector.connect((con) -> {
try (PreparedStatement ps = con.prepareStatement("DELETE FROM " + this.anchorTable +
" WHERE id =?;")) {
ps.setInt(1, anchor.getDbId());
ps.executeUpdate();
resolveUpdateCallback(callback, null);
} catch (Exception ex) {
resolveUpdateCallback(callback, ex);
}
})
);
}
public static String getTableName(String prefix, String name) {
String result = prefix + name;
if (!result.matches("[a-z0-9_]+")) {
throw new IllegalStateException("The generated table name '" + result + "' contains invalid characters");
}
return result;
}
private Anchor extractAnchor(ResultSet rs) throws SQLException {
String ownerStr = rs.getString("owner");
return new Anchor(rs.getInt("id"),
ownerStr != null ? UUID.fromString(ownerStr) : null,
new Location(Bukkit.getWorld(rs.getString("world_name")),
rs.getInt("x"),
rs.getInt("y"),
rs.getInt("z")),
rs.getInt("ticks_left"));
}
private void resolveUpdateCallback(@Nullable UpdateCallback callback, @Nullable Exception ex) {
if (callback != null) {
callback.accept(ex);
} else if (ex != null) {
Utils.logException(this.plugin, ex, "SQLite");
}
}
private void resolveCallback(@Nullable Callback<?> callback, @NotNull Exception ex) {
if (callback != null) {
callback.accept(ex, null);
} else {
Utils.logException(this.plugin, ex, "SQLite");
}
}
}

View File

@ -1,62 +1,72 @@
package com.songoda.epicanchors.settings;
package com.songoda.epicanchors.files;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.configuration.Config;
import com.songoda.core.configuration.ConfigSetting;
import com.songoda.core.hooks.EconomyManager;
import com.songoda.epicanchors.EpicAnchors;
import java.util.stream.Collectors;
import org.bukkit.plugin.java.JavaPlugin;
public class Settings {
private Settings() {
throw new IllegalStateException("Utility class");
}
static final Config config = EpicAnchors.getInstance().getCoreConfig();
public static final Config config = JavaPlugin.getPlugin(EpicAnchors.class).getCoreConfig();
public static final ConfigSetting NAMETAG = new ConfigSetting(config, "Main.Name Tag", "&6Anchor &8(&7{REMAINING}&8)",
public static final ConfigSetting NAME_TAG = new ConfigSetting(config, "Main.Name Tag",
"&6Anchor &8(&7{REMAINING}&8)",
"The anchor name tag used on the item and in the hologram.");
public static final ConfigSetting LORE = new ConfigSetting(config, "Main.Anchor Lore", "&7Place down to keep that chunk|&7loaded until the time runs out.",
public static final ConfigSetting LORE = new ConfigSetting(config, "Main.Anchor Lore",
"&7Place down to keep that chunk\n&7loaded until the time runs out.",
"The lore on the anchor item.");
public static final ConfigSetting MATERIAL = new ConfigSetting(config, "Main.Anchor Block Material", CompatibleMaterial.END_PORTAL_FRAME.getMaterial().name(),
"The material an anchor is represented with?");
public static final ConfigSetting MATERIAL = new ConfigSetting(config, "Main.Anchor Block Material",
CompatibleMaterial.END_PORTAL_FRAME.name(), "The material an anchor is represented with?");
public static final ConfigSetting ADD_TIME_WITH_XP = new ConfigSetting(config, "Main.Add Time With XP", true,
"Should players be able to add time to their anchors by using experience?");
public static final ConfigSetting XP_COST = new ConfigSetting(config, "Main.XP Cost", 10,
"The cost in experience levels to add 30 minutes to an anchor.");
public static final ConfigSetting ADD_TIME_WITH_ECONOMY = new ConfigSetting(config, "Main.Add Time With Economy", true,
"Should players be able to add time to their anchors",
"by using economy?");
"Should players be able to add time to their anchors by using economy?");
public static final ConfigSetting ECONOMY_COST = new ConfigSetting(config, "Main.Economy Cost", 5000.0,
"The cost in economy to add 30 minutes to an anchor.");
public static final ConfigSetting ADD_TIME_WITH_XP = new ConfigSetting(config, "Main.Add Time With XP", true,
"Should players be able to add time to their anchors",
"by using experience?");
public static final ConfigSetting XP_COST = new ConfigSetting(config, "Main.XP Cost", 10,
"The cost in experience to add 30 minutes to an anchor.");
public static final ConfigSetting ALLOW_ANCHOR_BREAKING = new ConfigSetting(config, "Main.Allow Anchor Breaking", false,
"Should players be able to break anchors?");
"Should players be able to break anchors and get an item dropped?");
public static final ConfigSetting HOLOGRAMS = new ConfigSetting(config, "Main.Holograms", true,
"Toggle holograms showing above anchors.");
@SuppressWarnings("unchecked")
public static final ConfigSetting ECONOMY_PLUGIN = new ConfigSetting(config, "Main.Economy", EconomyManager.getEconomy() == null ? "Vault" : EconomyManager.getEconomy().getName(),
"Which economy plugin should be used?",
"Supported plugins you have installed: \"" + EconomyManager.getManager().getRegisteredPlugins().stream().collect(Collectors.joining("\", \"")) + "\".");
"Supported plugins you have installed: \"" + String.join(", ", EconomyManager.getManager().getRegisteredPlugins()) + "\".");
public static final ConfigSetting ECO_ICON = new ConfigSetting(config, "Interfaces.Economy Icon", "SUNFLOWER",
public static final ConfigSetting ECO_ICON = new ConfigSetting(config, "Interfaces.Economy Icon", CompatibleMaterial.SUNFLOWER.name(),
"Item to be displayed as the icon for economy upgrades.");
public static final ConfigSetting XP_ICON = new ConfigSetting(config, "Interfaces.XP Icon", "EXPERIENCE_BOTTLE",
public static final ConfigSetting XP_ICON = new ConfigSetting(config, "Interfaces.XP Icon", CompatibleMaterial.EXPERIENCE_BOTTLE.name(),
"Item to be displayed as the icon for XP upgrades.");
public static final ConfigSetting GLASS_TYPE_1 = new ConfigSetting(config, "Interfaces.Glass Type 1", "GRAY_STAINED_GLASS_PANE");
public static final ConfigSetting GLASS_TYPE_2 = new ConfigSetting(config, "Interfaces.Glass Type 2", "BLUE_STAINED_GLASS_PANE");
public static final ConfigSetting GLASS_TYPE_3 = new ConfigSetting(config, "Interfaces.Glass Type 3", "LIGHT_BLUE_STAINED_GLASS_PANE");
public static final ConfigSetting GLASS_TYPE_1 = new ConfigSetting(config, "Interfaces.Glass Type 1", CompatibleMaterial.GRAY_STAINED_GLASS_PANE.name());
public static final ConfigSetting GLASS_TYPE_2 = new ConfigSetting(config, "Interfaces.Glass Type 2", CompatibleMaterial.BLUE_STAINED_GLASS_PANE.name());
public static final ConfigSetting GLASS_TYPE_3 = new ConfigSetting(config, "Interfaces.Glass Type 3", CompatibleMaterial.LIGHT_BLUE_STAINED_GLASS_PANE.name());
public static final ConfigSetting LANGUGE_MODE = new ConfigSetting(config, "System.Language Mode", "en_US",
public static final ConfigSetting PARTICLE_DESTROY = new ConfigSetting(config, "Particles.Destroy",
CompatibleParticleHandler.ParticleType.LAVA.name());
public static final ConfigSetting PARTICLE_UPGRADE = new ConfigSetting(config, "Particles.Upgrade",
CompatibleParticleHandler.ParticleType.SPELL_WITCH.name());
public static final ConfigSetting PARTICLE_VISUALIZER = new ConfigSetting(config, "Particles.Visualizer",
CompatibleParticleHandler.ParticleType.VILLAGER_HAPPY.name());
public static final ConfigSetting LANGUAGE = new ConfigSetting(config, "System.Language Mode", "en_US",
"The enabled language file.",
"More language files (if available) can be found in the plugins data folder.");
@ -66,7 +76,8 @@ public class Settings {
*/
public static void setupConfig() {
config.load();
config.setAutoremove(true).setAutosave(true);
config.setAutoremove(true)
.setAutosave(true);
// convert glass pane settings
int color;
@ -91,4 +102,4 @@ public class Settings {
config.saveChanges();
}
}
}

View File

@ -0,0 +1,150 @@
package com.songoda.epicanchors.files.migration;
import com.songoda.core.configuration.Config;
import com.songoda.core.configuration.ConfigSection;
import com.songoda.core.database.DataMigration;
import com.songoda.core.database.DataMigrationManager;
import com.songoda.core.database.DatabaseConnector;
import com.songoda.epicanchors.files.DataManager;
import com.songoda.epicanchors.utils.ThreadSync;
import org.bukkit.Location;
import org.bukkit.plugin.Plugin;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
public class AnchorMigration extends DataMigrationManager {
private final DataManager dataManager;
public AnchorMigration(DatabaseConnector databaseConnector, DataManager dataManager, DataMigration... migrations) {
super(databaseConnector, dataManager, migrations);
this.dataManager = dataManager;
}
public void migrateLegacyData(Plugin plugin) {
long start = System.nanoTime();
AtomicBoolean abortMigration = new AtomicBoolean(false);
int migratedAnchors = 0;
Config legacyData = new Config(plugin, "data.yml");
if (legacyData.getFile().exists()) {
ThreadSync thSync = new ThreadSync();
legacyData.load();
ConfigSection cfgSection = legacyData.getConfigurationSection("Anchors");
if (cfgSection == null) return;
List<LegacyAnchorEntry> anchorQueue = new ArrayList<>();
for (String locationStr : cfgSection.getKeys(false)) {
int ticksLeft = cfgSection.getInt(locationStr + ".ticksLeft");
String[] locArgs = deserializeLegacyLocation(locationStr);
if (ticksLeft == -99) {
ticksLeft = -1;
}
if (locArgs.length == 0) {
abortMigration.set(true);
plugin.getLogger().warning(() -> "Error migrating anchor '" + locationStr + "': invalid format - expected 'worldName:X:Y:Z'");
break;
}
String worldName = locArgs[0];
int x = Location.locToBlock(Double.parseDouble(locArgs[1]));
int y = Location.locToBlock(Double.parseDouble(locArgs[2]));
int z = Location.locToBlock(Double.parseDouble(locArgs[3]));
int finalTicksLeft = ticksLeft;
dataManager.exists(worldName, x, y, z, (ex, anchorExists) -> {
if (ex == null) {
if (anchorExists) {
cfgSection.set(locationStr, null);
} else {
anchorQueue.add(new LegacyAnchorEntry(worldName, x, y, z, finalTicksLeft));
}
} else {
abortMigration.set(true);
plugin.getLogger().log(Level.WARNING, ex, () -> "Error migrating Anchor '" + locationStr + "' from '" +
legacyData.getFile().getName() + "'");
}
thSync.release();
});
thSync.waitForRelease();
thSync.reset();
if (abortMigration.get()) break;
++migratedAnchors;
}
if (!abortMigration.get()) {
int finalMigratedAnchors = migratedAnchors;
this.dataManager.migrateAnchor(anchorQueue, ex -> {
long end = System.nanoTime();
if (ex == null) {
try {
Files.deleteIfExists(legacyData.getFile().toPath());
} catch (IOException err) {
plugin.getLogger().warning("Could not delete '" + legacyData.getFile().getName() + "' after data migration: " + err.getMessage());
}
plugin.getLogger().info("Successfully migrated " + finalMigratedAnchors + " Anchors from '" +
legacyData.getFile().getName() + "' (" + TimeUnit.NANOSECONDS.toMillis(end - start) + "ms)");
} else {
legacyData.save();
}
});
}
}
}
private String[] deserializeLegacyLocation(String str) {
if (str == null || str.isEmpty()) {
return new String[0];
}
str = str
.replace("w:", "")
.replace("x:", ":")
.replace("y:", ":")
.replace("z:", ":")
.replace("/", ".");
String[] args = str.split(":");
return args.length == 4 ? args : new String[0];
}
public static class LegacyAnchorEntry {
public final String worldName;
public final int x;
public final int y;
public final int z;
public final int ticksLeft;
public LegacyAnchorEntry(String worldName, int x, int y, int z, int ticksLeft) {
this.worldName = worldName;
this.x = x;
this.y = y;
this.z = z;
this.ticksLeft = ticksLeft;
}
}
}

View File

@ -0,0 +1,30 @@
package com.songoda.epicanchors.files.migration;
import com.songoda.core.database.DataMigration;
import com.songoda.epicanchors.files.DataManager;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class _1_InitialMigration extends DataMigration {
public _1_InitialMigration() {
super(1);
}
@Override
public void migrate(Connection connection, String tablePrefix) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + DataManager.getTableName(tablePrefix, "anchors") + "(" +
"id INTEGER NOT NULL," +
"world_name TEXT NOT NULL," +
"x INTEGER NOT NULL," +
"y INTEGER NOT NULL," +
"z INTEGER NOT NULL," +
"ticks_left INTEGER NOT NULL," +
"owner VARCHAR(36)," +
"PRIMARY KEY(id AUTOINCREMENT)" +
");");
}
}
}

View File

@ -1,92 +0,0 @@
package com.songoda.epicanchors.gui;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.hooks.EconomyManager;
import com.songoda.core.utils.TextUtils;
import com.songoda.core.utils.TimeUtils;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.settings.Settings;
import com.songoda.epicanchors.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class GUIOverview extends Gui {
private final EpicAnchors plugin;
private final Anchor anchor;
private final Player player;
private int task;
public GUIOverview(EpicAnchors plugin, Anchor anchor, Player player) {
this.plugin = plugin;
this.anchor = anchor;
this.player = player;
this.setRows(3);
this.setTitle(TextUtils.formatText(plugin.getLocale().getMessage("interface.anchor.title").getMessage()));
runTask();
constructGUI();
this.setOnClose(action -> Bukkit.getScheduler().cancelTask(task));
}
private void constructGUI() {
ItemStack glass1 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_1.getMaterial());
ItemStack glass2 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_2.getMaterial());
ItemStack glass3 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_3.getMaterial());
setDefaultItem(glass1);
mirrorFill(0, 0, true, true, glass2);
mirrorFill(0, 1, true, true, glass2);
mirrorFill(0, 2, true, true, glass3);
mirrorFill(1, 0, false, true, glass2);
mirrorFill(1, 1, false, true, glass3);
setItem(13, GuiUtils.createButtonItem(plugin.makeAnchorItem(anchor.getTicksLeft()),
plugin.getLocale().getMessage("interface.anchor.smalltitle").getMessage(),
(anchor.isInfinite()) ? ChatColor.GRAY + "Infinite" : ChatColor.GRAY + TimeUtils.makeReadable((long) (anchor.getTicksLeft() / 20) * 1000) + " remaining."));
if (EconomyManager.isEnabled() && Settings.ADD_TIME_WITH_ECONOMY.getBoolean()) {
setButton(15, GuiUtils.createButtonItem(Settings.ECO_ICON.getMaterial(CompatibleMaterial.SUNFLOWER),
plugin.getLocale().getMessage("interface.button.addtimewitheconomy").getMessage(),
plugin.getLocale().getMessage("interface.button.addtimewitheconomylore")
.processPlaceholder("cost", EconomyManager.formatEconomy(Settings.ECONOMY_COST.getDouble())).getMessage()), // EconomyManager.formatEconomy adds its own prefix/suffix
event -> checkInfiniteAndAlert(anchor, event.player, true));
}
if (Settings.ADD_TIME_WITH_XP.getBoolean()) {
setButton(11, GuiUtils.createButtonItem(Settings.XP_ICON.getMaterial(CompatibleMaterial.EXPERIENCE_BOTTLE),
plugin.getLocale().getMessage("interface.button.addtimewithxp").getMessage(),
plugin.getLocale().getMessage("interface.button.addtimewithxplore")
.processPlaceholder("cost", String.valueOf(Settings.XP_COST.getInt())).getMessage()),
event -> checkInfiniteAndAlert(anchor, event.player, false));
}
}
private void runTask() {
task = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
updateItem(13, plugin.getLocale().getMessage("interface.anchor.smalltitle").getMessage(),
(anchor.isInfinite()) ? ChatColor.GRAY + "Infinite" : ChatColor.GRAY + TimeUtils.makeReadable((long) (anchor.getTicksLeft() / 20) * 1000) + " remaining.");
}, 5L, 5L);
}
private void checkInfiniteAndAlert(Anchor anchor, Player p, boolean eco) {
if (anchor.isInfinite()) {
plugin.getLocale().getMessage("interface.button.infinite").sendPrefixedMessage(p);
} else {
if (eco) {
anchor.addTime("ECO", p);
} else {
anchor.addTime("XP", p);
}
}
}
}

View File

@ -0,0 +1,135 @@
package com.songoda.epicanchors.guis;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.compatibility.CompatibleSound;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.hooks.EconomyManager;
import com.songoda.core.utils.TextUtils;
import com.songoda.core.utils.TimeUtils;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.files.Settings;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class AnchorGui extends Gui {
private final EpicAnchors plugin;
private final Anchor anchor;
public AnchorGui(EpicAnchors plugin, Anchor anchor) {
this.plugin = plugin;
this.anchor = anchor;
setRows(3);
setTitle(TextUtils.formatText(plugin.getLocale().getMessage("interface.anchor.title").getMessage()));
constructGUI();
runPreparedGuiTask(this.plugin, this, this.anchor);
}
private void constructGUI() {
prepareGui(this.plugin, this, this.anchor);
if (Settings.ADD_TIME_WITH_XP.getBoolean()) {
String itemName = plugin.getLocale().getMessage("interface.button.addtimewithxp").getMessage();
String itemLore = plugin.getLocale().getMessage("interface.button.addtimewithxplore")
.processPlaceholder("cost", Settings.XP_COST.getInt())
.getMessage();
setButton(11,
GuiUtils.createButtonItem(Settings.XP_ICON.getMaterial(CompatibleMaterial.EXPERIENCE_BOTTLE), itemName, itemLore),
event -> buyTime(anchor, event.player, false));
}
if (EconomyManager.isEnabled() && Settings.ADD_TIME_WITH_ECONOMY.getBoolean()) {
String itemName = plugin.getLocale().getMessage("interface.button.addtimewitheconomy").getMessage();
String itemLore = plugin.getLocale().getMessage("interface.button.addtimewitheconomylore")
// EconomyManager#formatEconomy adds its own prefix/suffix
.processPlaceholder("cost", EconomyManager.formatEconomy(Settings.ECONOMY_COST.getDouble()))
.getMessage();
setButton(15,
GuiUtils.createButtonItem(Settings.ECO_ICON.getMaterial(CompatibleMaterial.SUNFLOWER), itemName, itemLore),
event -> buyTime(anchor, event.player, true));
}
}
private void buyTime(Anchor anchor, Player p, boolean eco) {
if (anchor.isInfinite()) {
this.plugin.getLocale().getMessage("interface.button.infinite").sendPrefixedMessage(p);
} else {
boolean success = false;
if (eco) {
double cost = Settings.ECONOMY_COST.getDouble();
success = EconomyManager.withdrawBalance(p, cost);
} else {
int cost = Settings.XP_COST.getInt();
if (p.getLevel() >= cost || p.getGameMode() == GameMode.CREATIVE) {
if (p.getGameMode() != GameMode.CREATIVE) {
p.setLevel(p.getLevel() - cost);
}
success = true;
}
}
if (success) {
anchor.addTicksLeft(20 * 60 * 30); // 30 minutes
p.playSound(p.getLocation(), CompatibleSound.ENTITY_PLAYER_LEVELUP.getSound(), 0.6F, 15);
CompatibleParticleHandler.spawnParticles(CompatibleParticleHandler.ParticleType.getParticle(Settings.PARTICLE_UPGRADE.getString()),
anchor.getLocation().add(.5, .5, .5), 100, .5, .5, .5);
} else {
this.plugin.getLocale().getMessage("event.upgrade.cannotafford").sendPrefixedMessage(p);
}
}
}
protected static void prepareGui(EpicAnchors plugin, Gui gui, Anchor anchor) {
ItemStack glass1 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_1.getMaterial());
ItemStack glass2 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_2.getMaterial());
ItemStack glass3 = GuiUtils.getBorderItem(Settings.GLASS_TYPE_3.getMaterial());
gui.setDefaultItem(glass1);
gui.mirrorFill(0, 0, true, true, glass2);
gui.mirrorFill(0, 1, true, true, glass2);
gui.mirrorFill(0, 2, true, true, glass3);
gui.mirrorFill(1, 0, false, true, glass2);
gui.mirrorFill(1, 1, false, true, glass3);
String itemName = plugin.getLocale().getMessage("interface.anchor.smalltitle").getMessage();
String itemLore = anchor.isInfinite() ?
ChatColor.GRAY + "Infinite" :
ChatColor.GRAY + TimeUtils.makeReadable((long) ((anchor.getTicksLeft() / 20.0) * 1000)) + " remaining.";
gui.setItem(13, GuiUtils.createButtonItem(plugin.getAnchorManager().createAnchorItem(
anchor.getTicksLeft(), anchor.getLocation().getBlock().getType()),
itemName, itemLore));
}
protected static void runPreparedGuiTask(EpicAnchors plugin, Gui gui, Anchor anchor) {
int taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
if (anchor.getTicksLeft() == 0) {
gui.close();
} else {
String itemName = plugin.getLocale().getMessage("interface.anchor.smalltitle").getMessage();
String itemLore = anchor.isInfinite() ?
ChatColor.GRAY + "Infinite" :
ChatColor.GRAY + TimeUtils.makeReadable((long) ((anchor.getTicksLeft() / 20.0) * 1000)) + " remaining.";
gui.updateItem(13, itemName, itemLore);
}
}, 0, 20);
gui.setOnClose(action -> Bukkit.getScheduler().cancelTask(taskId));
}
}

View File

@ -0,0 +1,64 @@
package com.songoda.epicanchors.guis;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.gui.methods.Closable;
import com.songoda.core.utils.TextUtils;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.files.Settings;
import com.songoda.epicanchors.utils.Callback;
public class DestroyConfirmationGui extends Gui {
private final EpicAnchors plugin;
private final Anchor anchor;
private Callback<Boolean> handler;
public DestroyConfirmationGui(EpicAnchors plugin, Anchor anchor, Callback<Boolean> callback) {
this.plugin = plugin;
this.anchor = anchor;
this.handler = (ex, result) -> {
this.handler = null;
this.close();
callback.accept(ex, result);
};
this.setRows(3);
this.setTitle(TextUtils.formatText(plugin.getLocale().getMessage("interface.anchor.title").getMessage()));
constructGUI();
AnchorGui.runPreparedGuiTask(this.plugin, this, this.anchor);
Closable currClosable = this.closer;
this.closer = event -> {
currClosable.onClose(event);
if (this.handler != null) {
this.handler.accept(null, false);
}
};
}
private void constructGUI() {
AnchorGui.prepareGui(this.plugin, this, this.anchor);
String cancelLore = plugin.getLocale().getMessage("interface.button.cancelDestroyLore").getMessage();
String confirmLore = plugin.getLocale().getMessage("interface.button." +
(Settings.ALLOW_ANCHOR_BREAKING.getBoolean() ? "confirmDestroyLore" : "confirmDestroyLoreNoDrops"))
.getMessage();
setButton(11, GuiUtils.createButtonItem(CompatibleMaterial.RED_TERRACOTTA,
plugin.getLocale().getMessage("interface.button.cancelDestroy").getMessage(),
cancelLore.isEmpty() ? new String[0] : new String[] {cancelLore}),
event -> this.handler.accept(null, false));
setButton(15, GuiUtils.createButtonItem(CompatibleMaterial.GREEN_TERRACOTTA,
plugin.getLocale().getMessage("interface.button.confirmDestroy").getMessage(),
confirmLore.isEmpty() ? new String[0] : new String[] {confirmLore}),
event -> this.handler.accept(null, true));
}
}

View File

@ -0,0 +1,126 @@
package com.songoda.epicanchors.listener;
import com.songoda.core.compatibility.CompatibleHand;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.compatibility.CompatibleSound;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.AnchorManager;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.files.Settings;
import com.songoda.epicanchors.guis.AnchorGui;
import com.songoda.epicanchors.guis.DestroyConfirmationGui;
import com.songoda.epicanchors.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class AnchorListener implements Listener {
private final EpicAnchors plugin;
public AnchorListener(EpicAnchors instance) {
this.plugin = instance;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent e) {
ItemStack item = e.getItemInHand();
if (item.hasItemMeta() &&
item.getItemMeta().hasDisplayName() &&
Settings.MATERIAL.getMaterial().getMaterial() == e.getBlock().getType()) {
if (!plugin.getAnchorManager().isReady(e.getBlock().getWorld())) {
e.setCancelled(true);
e.getPlayer().sendMessage("Anchors are still being initialized - Please wait a moment"); // TODO
} else {
int ticksLeft = AnchorManager.getTicksFromItem(item);
if (ticksLeft != 0) {
boolean dropOnErr = e.getPlayer().getGameMode() != GameMode.CREATIVE;
plugin.getAnchorManager().createAnchor(e.getBlock().getLocation(), e.getPlayer().getUniqueId(), ticksLeft,
(ex, result) -> {
if (ex != null) {
Utils.logException(this.plugin, ex, "SQLite");
e.getPlayer().sendMessage("Error creating anchor!"); // TODO
Bukkit.getScheduler().runTask(this.plugin, () -> {
if (Settings.MATERIAL.getMaterial().getMaterial() == e.getBlock().getType()) {
e.getBlock().setType(Material.AIR);
}
if (dropOnErr) {
e.getBlock().getWorld().dropItemNaturally(e.getBlock().getLocation(),
plugin.getAnchorManager().createAnchorItem(ticksLeft, item.getType()));
}
});
}
});
}
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockInteract(PlayerInteractEvent e) {
if (e.getClickedBlock() == null ||
!plugin.getAnchorManager().isReady(e.getClickedBlock().getWorld())) return;
Player p = e.getPlayer();
Anchor anchor = plugin.getAnchorManager().getAnchor(e.getClickedBlock());
if (anchor != null) {
e.setCancelled(true);
if (p.hasPermission("EpicAnchors.admin") ||
this.plugin.getAnchorManager().hasAccess(anchor, p)) {
if (e.getAction() == Action.LEFT_CLICK_BLOCK) { // Destroy anchor
this.plugin.getGuiManager().showGUI(e.getPlayer(),
new DestroyConfirmationGui(this.plugin, anchor, (ex, result) -> {
if (result) {
BlockBreakEvent blockBreakEvent = new BlockBreakEvent(e.getClickedBlock(), p);
Bukkit.getPluginManager().callEvent(blockBreakEvent);
if (!blockBreakEvent.isCancelled()) {
plugin.getAnchorManager().destroyAnchor(anchor);
}
}
}));
} else if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { // Manage anchor
ItemStack item = CompatibleHand.MAIN_HAND.getItem(e.getPlayer());
int itemTicks = AnchorManager.getTicksFromItem(item);
if (itemTicks != 0) {
if (!anchor.isInfinite()) {
if (itemTicks == -1) {
anchor.setTicksLeft(-1);
} else {
anchor.addTicksLeft(itemTicks);
}
if (p.getGameMode() != GameMode.CREATIVE) {
CompatibleHand.MAIN_HAND.takeItem(p, 1);
}
p.playSound(p.getLocation(), CompatibleSound.ENTITY_PLAYER_LEVELUP.getSound(), .6F, 15);
CompatibleParticleHandler.spawnParticles(CompatibleParticleHandler.ParticleType.getParticle(Settings.PARTICLE_UPGRADE.getString()),
anchor.getLocation().add(.5, .5, .5), 100, .5, .5, .5);
}
} else {
plugin.getGuiManager().showGUI(p, new AnchorGui(plugin, anchor));
}
}
} else {
plugin.getLocale().getMessage("event.general.nopermission").sendMessage(p);
}
}
}
}

View File

@ -0,0 +1,77 @@
package com.songoda.epicanchors.listener;
import com.songoda.epicanchors.AnchorManager;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.world.PortalCreateEvent;
public class BlockListener implements Listener {
private final AnchorManager manager;
public BlockListener(AnchorManager manager) {
this.manager = manager;
}
@EventHandler(ignoreCancelled = true)
private void onBlockBurn(BlockBurnEvent e) {
if (!this.manager.isReady(e.getBlock().getWorld())) return;
if (manager.isAnchor(e.getBlock())) {
e.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
private void onBlockPiston(BlockPistonExtendEvent e) {
if (!this.manager.isReady(e.getBlock().getWorld())) return;
if (manager.isAnchor(e.getBlock())) {
e.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
private void onBlockPiston(BlockPistonRetractEvent e) {
if (!this.manager.isReady(e.getBlock().getWorld())) return;
if (manager.isAnchor(e.getBlock())) {
e.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
private void onBlockPhysics(BlockPhysicsEvent e) {
if (!this.manager.isReady(e.getBlock().getWorld())) return;
if (manager.isAnchor(e.getBlock())) {
e.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
private void onBlockPiston(LeavesDecayEvent e) {
if (!this.manager.isReady(e.getBlock().getWorld())) return;
if (manager.isAnchor(e.getBlock())) {
e.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
public void onPortalCreation(PortalCreateEvent e) {
if (!this.manager.isReady(e.getWorld())) return;
for (Block b : e.getBlocks()) {
if (manager.isAnchor(b)) {
e.setCancelled(true);
break;
}
}
}
}

View File

@ -0,0 +1,154 @@
package com.songoda.epicanchors.listener;
import com.songoda.epicanchors.EpicAnchors;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockGrowEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.SpawnerSpawnEvent;
import org.bukkit.event.inventory.FurnaceSmeltEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class DebugListener implements Listener {
private final EpicAnchors plugin;
private final Logger logger;
public DebugListener(EpicAnchors plugin) {
this.plugin = plugin;
this.logger = Logger.getLogger(plugin.getName() + "-DEBUG");
}
private void logDebug(String s) {
LogRecord logRecord = new LogRecord(Level.INFO, s);
logRecord.setMessage("[" + this.logger.getName() + "] " + logRecord.getMessage());
this.logger.log(logRecord);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onBlockPhysics(BlockPhysicsEvent e) {
if (skipEvent(e.getBlock().getChunk())) return;
logDebug("BlockPhysicsEvent (" + e.getBlock().getType() + ")");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onBlockForm(BlockFormEvent e) {
if (skipEvent(e.getBlock().getChunk())) return;
logDebug("BlockFormEvent (" + e.getBlock().getType() + " -> " + e.getNewState().getType() + ")");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onBlockFromTo(BlockFromToEvent e) {
if (skipEvent(e.getBlock().getChunk())) return;
logDebug("BlockFromToEvent");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onFurnace(FurnaceSmeltEvent e) {
if (skipEvent(e.getBlock().getChunk())) return;
logDebug("FurnaceSmeltEvent");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onSneak(PlayerToggleSneakEvent e) {
Chunk chunk = e.getPlayer().getLocation().getChunk();
if (e.getPlayer().isFlying() || skipEvent(chunk)) return;
Map<String, Integer> count = new HashMap<>();
if (e.isSneaking()) {
e.getPlayer().sendMessage("§e§lEntities");
for (Entity entity : chunk.getEntities()) {
count.compute(entity.getType().name(), (k, v) -> v == null ? 1 : v + 1);
}
} else {
e.getPlayer().sendMessage("§e§lTileEntities");
for (BlockState blockState : chunk.getTileEntities()) {
count.compute(blockState.getType().name(), (k, v) -> v == null ? 1 : v + 1);
}
}
Map<String, Integer> m = count.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
for (Map.Entry<String, Integer> entry : m.entrySet()) {
String entityName = WordUtils.capitalize(entry.getKey().toLowerCase(), new char[] {'_'}).replace("_", "");
e.getPlayer().sendMessage("§a" + entityName + "§7:§r " + entry.getValue());
}
e.getPlayer().sendMessage("");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onSpawner(SpawnerSpawnEvent e) {
if (skipEvent(e.getSpawner().getBlock().getChunk())) return;
logDebug("SpawnerSpawnEvent (" + e.getEntity().getType().name() + ")");
}
@EventHandler
private void onCreatureSpawn(CreatureSpawnEvent e) {
if (skipEvent(e.getLocation().getChunk())) return;
logDebug("CreatureSpawnEvent (" + e.getEntity().getType().name() + ")");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onBlockGrow(BlockGrowEvent e) {
if (skipEvent(e.getBlock().getChunk())) return;
logDebug("BlockGrowEvent (" + e.getBlock().getType() + ")");
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onInvItemMove(InventoryMoveItemEvent e) {
if (e.getSource().getHolder() == null) return;
Location loc = null;
try {
loc = (Location) e.getSource().getHolder().getClass().getDeclaredMethod("getLocation").invoke(e.getSource().getHolder());
} catch (Exception ex) {
try {
loc = (Location) e.getSource().getClass().getDeclaredMethod("getLocation").invoke(e.getSource());
} catch (Exception ex2) {
logDebug("InventoryMoveItemEvent (Potentially in a chunk without Anchor [Not supported at current server version])");
}
}
if (loc == null || skipEvent(loc.getChunk())) return;
logDebug("InventoryMoveItemEvent (" + e.getSource().getType() + " -> " + e.getDestination().getType() + ")");
}
private boolean skipEvent(Chunk chunk) {
return !this.plugin.getAnchorManager().isReady(chunk.getWorld()) || !this.plugin.getAnchorManager().hasAnchor(chunk);
}
}

View File

@ -0,0 +1,30 @@
package com.songoda.epicanchors.listener;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import java.util.function.Consumer;
public class WorldListener implements Listener {
private final Consumer<World> initAnchorsInWorld;
private final Consumer<World> deInitAnchorsInWorld;
public WorldListener(Consumer<World> initAnchorsInWorld, Consumer<World> deInitAnchorsInWorld) {
this.initAnchorsInWorld = initAnchorsInWorld;
this.deInitAnchorsInWorld = deInitAnchorsInWorld;
}
@EventHandler(priority = EventPriority.MONITOR)
private void onWorldLoad(WorldLoadEvent e) {
initAnchorsInWorld.accept(e.getWorld());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onWorldUnload(WorldUnloadEvent e) {
deInitAnchorsInWorld.accept(e.getWorld());
}
}

View File

@ -1,40 +0,0 @@
package com.songoda.epicanchors.listeners;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.settings.Settings;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.inventory.ItemStack;
public class BlockListeners implements Listener {
private EpicAnchors plugin;
public BlockListeners(EpicAnchors instance) {
this.plugin = instance;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) {
ItemStack item = event.getItemInHand();
if (!item.hasItemMeta()
|| !item.getItemMeta().hasDisplayName()
|| Settings.MATERIAL.getMaterial().getMaterial() != event.getBlock().getType()
|| plugin.getTicksFromItem(item) == 0) return;
Anchor anchor = new Anchor(event.getBlock().getLocation(), plugin.getTicksFromItem(item));
if (plugin.getTicksFromItem(item) == -99) {
anchor.setInfinite(true);
}
plugin.getAnchorManager().addAnchor(event.getBlock().getLocation(), anchor);
plugin.updateHologram(anchor);
}
}

View File

@ -1,71 +0,0 @@
package com.songoda.epicanchors.listeners;
import com.songoda.core.compatibility.CompatibleHand;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.compatibility.CompatibleSound;
import com.songoda.core.utils.ItemUtils;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.gui.GUIOverview;
import com.songoda.epicanchors.settings.Settings;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class InteractListeners implements Listener {
private final EpicAnchors instance;
public InteractListeners(EpicAnchors instance) {
this.instance = instance;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockInteract(PlayerInteractEvent event) {
if (event.getClickedBlock() == null) return;
Anchor anchor = instance.getAnchorManager().getAnchor(event.getClickedBlock().getLocation());
if (anchor == null) return;
event.setCancelled(true);
Player player = event.getPlayer();
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
BlockBreakEvent blockBreakEvent = new BlockBreakEvent(event.getClickedBlock(), player);
Bukkit.getPluginManager().callEvent(blockBreakEvent);
if (blockBreakEvent.isCancelled())
return;
anchor.bust();
return;
}
ItemStack item = player.getItemInHand();
if (Settings.MATERIAL.getMaterial().matches(item)) {
if (instance.getTicksFromItem(item) == 0) return;
anchor.setTicksLeft(anchor.getTicksLeft() + instance.getTicksFromItem(item));
if (player.getGameMode() != GameMode.CREATIVE)
ItemUtils.takeActiveItem(player, CompatibleHand.getHand(event));
player.playSound(player.getLocation(), CompatibleSound.ENTITY_PLAYER_LEVELUP.getSound(), 0.6F, 15.0F);
CompatibleParticleHandler.spawnParticles(CompatibleParticleHandler.ParticleType.SPELL_WITCH, anchor.getLocation().add(.5, .5, .5), 100, .5, .5, .5);
} else {
instance.getGuiManager().showGUI(player, new GUIOverview(EpicAnchors.getInstance(), anchor, player));
}
}
}

View File

@ -1,21 +0,0 @@
package com.songoda.epicanchors.listeners;
import com.songoda.epicanchors.EpicAnchors;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.PortalCreateEvent;
public class PortalListeners implements Listener {
private EpicAnchors plugin;
public PortalListeners(EpicAnchors instance) {
this.plugin = instance;
}
@EventHandler
public void onPortalCreation(PortalCreateEvent e) {
if (e.getBlocks().size() < 1) return;
if (plugin.getAnchorManager().isAnchor(e.getBlocks().get(0).getLocation())) e.setCancelled(true);
}
}

View File

@ -1,145 +1,122 @@
package com.songoda.epicanchors.tasks;
import com.songoda.core.compatibility.CompatibleMaterial;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.nms.NmsManager;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.AnchorManager;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.settings.Settings;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import com.songoda.epicanchors.utils.Utils;
import com.songoda.epicanchors.utils.WorldUtils;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
/**
* More information about what types of game ticks there are and what does what: https://minecraft.fandom.com/wiki/Tick
*/
public class AnchorTask extends BukkitRunnable {
private static final int TASK_INTERVAL = 3;
private static EpicAnchors plugin;
private final EpicAnchors plugin;
private final AnchorManager anchorManager;
private Map<Location, Integer> delays = new HashMap<>();
private boolean randomTicksFailed;
private boolean spawnerTicksFailed;
private Class<?> clazzEntity, clazzCraftEntity, clazzMinecraftServer;
private Method methodTick, methodGetHandle;
private Field fieldCurrentTick, fieldActivatedTick;
private boolean epicSpawners;
public AnchorTask(EpicAnchors plug) {
plugin = plug;
epicSpawners = Bukkit.getPluginManager().getPlugin("EpicSpawners") != null;
try {
String ver = Bukkit.getServer().getClass().getPackage().getName().substring(23);
clazzMinecraftServer = Class.forName("net.minecraft.server." + ver + ".MinecraftServer");
clazzEntity = Class.forName("net.minecraft.server." + ver + ".Entity");
clazzCraftEntity = Class.forName("org.bukkit.craftbukkit." + ver + ".entity.CraftEntity");
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13))
methodTick = clazzEntity.getDeclaredMethod("tick");
else if (ServerVersion.isServerVersion(ServerVersion.V1_12))
methodTick = clazzEntity.getDeclaredMethod("B_");
else if (ServerVersion.isServerVersion(ServerVersion.V1_11))
methodTick = clazzEntity.getDeclaredMethod("A_");
else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9))
methodTick = clazzEntity.getDeclaredMethod("m");
else
methodTick = clazzEntity.getDeclaredMethod("t_");
methodGetHandle = clazzCraftEntity.getDeclaredMethod("getHandle");
fieldCurrentTick = clazzMinecraftServer.getDeclaredField("currentTick");
fieldActivatedTick = clazzEntity.getDeclaredField("activatedTick");
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
this.runTaskTimer(plugin, 0, 3);
public AnchorTask(EpicAnchors plugin) {
this.plugin = plugin;
this.anchorManager = plugin.getAnchorManager();
}
private void doParticle() {
for (Anchor anchor : plugin.getAnchorManager().getAnchors().values()) {
Location location1 = anchor.getLocation().add(.5, .5, .5);
if (location1.getWorld() == null) continue;
CompatibleParticleHandler.redstoneParticles(location1, 255, 255, 255, 1.2F, 5, .75F);
}
public void startTask() {
runTaskTimer(this.plugin, TASK_INTERVAL, TASK_INTERVAL);
}
@Override
public void run() {
doParticle();
for (Anchor anchor : new ArrayList<>(plugin.getAnchorManager().getAnchors().values())) {
try {
for (World world : Bukkit.getWorlds()) {
if (!this.anchorManager.isReady(world)) return;
if (anchor.getLocation() == null) continue;
int randomTicks = WorldUtils.getRandomTickSpeed(world) * TASK_INTERVAL;
plugin.updateHologram(anchor);
Set<Chunk> alreadyTicked = new HashSet<>();
Anchor[] anchorsInWorld = this.anchorManager.getAnchors(world);
List<Anchor> toUpdateHolo = new ArrayList<>(anchorsInWorld.length);
Location location = anchor.getLocation();
if (CompatibleMaterial.getMaterial(location.getBlock()) != Settings.MATERIAL.getMaterial())
continue;
Chunk chunk = location.getChunk();
chunk.load();
// Load entities
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof LivingEntity) || entity instanceof Player) continue;
if (entity.getNearbyEntities(32, 32, 32).stream().anyMatch(entity1 -> entity1 instanceof Player)) {
continue;
// Skip all chunks with players in them
for (Player pInWorld : world.getPlayers()) {
alreadyTicked.add(pInWorld.getLocation().getChunk());
}
try {
Object objCraftEntity = clazzCraftEntity.cast(entity);
Object objEntity = methodGetHandle.invoke(objCraftEntity);
for (Anchor anchor : anchorsInWorld) {
Chunk chunk = anchor.getChunk();
fieldActivatedTick.set(objEntity, fieldCurrentTick.getLong(objEntity));
methodTick.invoke(objEntity);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
// Tick the anchor's chunk (but not multiple times)
if (alreadyTicked.add(chunk)) {
// Having a chunk loaded takes care of entities and weather (https://minecraft.fandom.com/wiki/Tick#Chunk_tick)
if (!chunk.isLoaded()) {
// Loading an already loaded chunk still fires the ChunkLoadEvent and might have a huge
// impact on performance if other plugins do not expect that either...
WorldUtils.loadAnchoredChunk(chunk, this.plugin);
}
if (!randomTicksFailed) {
try {
NmsManager.getWorld().randomTickChunk(chunk, randomTicks);
} catch (NoSuchFieldException | IllegalAccessException ex) {
this.plugin.getLogger().log(Level.FINER, ex,
() -> "Failed to do random ticks on this server implementation(/version) - " +
"Skipping further random ticks.");
randomTicksFailed = true;
}
}
if (!spawnerTicksFailed) {
try {
NmsManager.getWorld().tickInactiveSpawners(chunk, TASK_INTERVAL);
} catch (NoSuchFieldException | IllegalAccessException ex) {
this.plugin.getLogger().log(Level.FINER, ex,
() -> "Failed to do spawner ticks on this server implementation(/version) - " +
"Skipping further spawner ticks.");
spawnerTicksFailed = true;
}
}
}
// TODO: Only update hologram if a player is nearby
// Simplify player location to chunks to potentially group players
// Use the server view distance to calculate minimum distance to count as not-nearby
// Destroy anchors and queue hologram update
if (!anchor.isInfinite()) {
int ticksLeft = anchor.removeTicksLeft(3);
if (ticksLeft == 0) {
this.anchorManager.destroyAnchor(anchor);
} else {
toUpdateHolo.add(anchor);
}
} else {
toUpdateHolo.add(anchor);
}
}
// Update holograms on queued anchors
anchorManager.updateHolograms(toUpdateHolo);
}
int ticksLeft = anchor.getTicksLeft();
if (!anchor.isInfinite()) {
anchor.setTicksLeft(ticksLeft - 3);
}
if (ticksLeft <= 0 && !anchor.isInfinite()) {
anchor.bust();
chunk.unload();
return;
}
if (!epicSpawners || com.songoda.epicspawners.EpicSpawners.getInstance().getSpawnerManager() == null) continue;
com.songoda.epicspawners.EpicSpawners.getInstance().getSpawnerManager().getSpawners().stream()
.filter(spawner -> spawner.getWorld().isChunkLoaded(spawner.getX() >> 4, spawner.getZ() >> 4)
&& chunk == spawner.getLocation().getChunk()).forEach(spawner -> {
Block block = spawner.getLocation().getBlock();
if (!delays.containsKey(block.getLocation())) {
delays.put(block.getLocation(), spawner.updateDelay());
return;
}
int delay = delays.get(block.getLocation());
delay -= 1;
delays.put(block.getLocation(), delay);
if (delay <= 0) {
spawner.spawn();
delays.remove(block.getLocation());
}
});
} catch (Exception ex) {
Utils.logException(this.plugin, ex);
}
}
}

View File

@ -1,102 +1,111 @@
package com.songoda.epicanchors.tasks;
import com.songoda.core.compatibility.CompatibleParticleHandler;
import com.songoda.epicanchors.Anchor;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.anchor.Anchor;
import com.songoda.epicanchors.anchor.AnchorManager;
import org.bukkit.*;
import com.songoda.epicanchors.files.Settings;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
public class VisualizeTask extends BukkitRunnable {
private static final int TASK_INTERVAL = 30;
private static VisualizeTask instance;
private static EpicAnchors plugin;
private final static Map<OfflinePlayer, Boolean> active = new ConcurrentHashMap();
private final static Random random = new Random();
int radius;
private final EpicAnchors plugin;
public VisualizeTask(EpicAnchors plug) {
plugin = plug;
radius = Bukkit.getServer().getViewDistance();
if (instance == null) {
instance = this;
instance.runTaskTimerAsynchronously(plugin, 60, 10);
}
private final int radius = Bukkit.getServer().getViewDistance();
public VisualizeTask(EpicAnchors plugin) {
this.plugin = plugin;
}
public static boolean togglePlayer(Player p) {
Boolean isActive = active.get(p);
active.put(p, isActive = (isActive == null || !isActive));
return isActive;
}
public static void removePlayer(Player p) {
active.remove(p);
public void startTask() {
runTaskTimer(plugin, TASK_INTERVAL, TASK_INTERVAL);
}
@Override
public void run() {
active.entrySet().stream()
.filter(e -> e.getValue() && e.getKey().isOnline())
.forEach(e -> particleTick((Player) e.getKey()));
}
HashMap<Chunk, Set<Player>> chunksToVisualize = new HashMap<>();
Set<Chunk> loadedChunks = new HashSet<>();
void particleTick(Player player) {
final AnchorManager anchorManager = plugin.getAnchorManager();
final Location playerLocation = player.getLocation();
final World world = playerLocation.getWorld();
// start and stop chunk coordinates
int startY = playerLocation.getBlockY() + 1;
int cxi = (playerLocation.getBlockX() >> 4) - radius, cxn = cxi + radius * 2;
int czi = (playerLocation.getBlockZ() >> 4) - radius, czn = czi + radius * 2;
// loop through the chunks to find applicable ones
for (int cx = cxi; cx < cxn; cx++) {
for (int cz = czi; cz < czn; cz++) {
// sanity check
if (!world.isChunkLoaded(cx, cz))
continue;
CompatibleParticleHandler.ParticleType particleType = CompatibleParticleHandler.ParticleType.getParticle(Settings.PARTICLE_VISUALIZER.getString());
// so! Is this a claimed chunk?
Anchor anchor = anchorManager.getAnchor(world.getName(), cx, cz);
if (anchor != null) {
// we found one!
// now we get to spawn the silly particles for the player
showChunkParticles(player, world.getChunkAt(cx, cz), startY);
for (World world : Bukkit.getWorlds()) {
if (!this.plugin.getAnchorManager().isReady(world)) continue;
loadedChunks.clear();
for (Anchor anchor : this.plugin.getAnchorManager().getAnchors(world)) {
loadedChunks.add(anchor.getChunk());
}
if (!loadedChunks.isEmpty()) {
for (Player p : world.getPlayers()) {
if (!this.plugin.getAnchorManager().hasChunksVisualized(p)) continue;
Location pLoc = p.getLocation();
// start and stop chunk coordinates
int cxi = (pLoc.getBlockX() >> 4) - radius;
int cxn = cxi + radius * 2;
int czi = (pLoc.getBlockZ() >> 4) - radius;
int czn = czi + radius * 2;
// loop through the chunks to find applicable ones
for (int cx = cxi; cx < cxn; ++cx) {
for (int cz = czi; cz < czn; ++cz) {
Chunk chunk = world.getChunkAt(cx, cz);
if (loadedChunks.contains(chunk)) {
chunksToVisualize.computeIfAbsent(chunk, k -> new HashSet<>())
.add(p);
}
}
}
}
}
}
}
void showChunkParticles(Player player, Chunk c, int startY) {
// loop through the chunk
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
// show about 1/5 of the blocks per tick
boolean show = random.nextFloat() < .2;
if (!show)
continue;
for (Map.Entry<Chunk, Set<Player>> entry : chunksToVisualize.entrySet()) {
int maxY = entry.getKey().getWorld().getMaxHeight();
// Exclude everything over max height
if (startY >= c.getWorld().getMaxHeight()) continue;
for (Player p : entry.getValue()) {
int startY = p.getLocation().getBlockY() + 2;
// only show if there is a space to show above a solid block
Block b = c.getBlock(x, startY, z);
int maxDown = 8;
do {
show = b.getType().isTransparent() && !(b = b.getRelative(BlockFace.DOWN)).getType().isTransparent();
} while (--maxDown > 0 && !show);
if (startY <= 0) continue;
// can we do this?
if (show) {
final Location loc = b.getLocation().add(.5, 1.5, .5);
// loop through the chunk
for (int x = 0; x < 16; ++x) {
for (int z = 0; z < 16; ++z) {
if (Math.random() < .125) { // Don't spawn particles on each block
if (startY >= maxY) {
startY = maxY - 1;
}
player.spawnParticle(Particle.VILLAGER_HAPPY, loc, 0, 0, 0, 0, 1);
Block b = entry.getKey().getBlock(x, startY, z);
for (int i = 0; i < 12; ++i) {
if (b.getType().isSolid()) break;
b = b.getRelative(BlockFace.DOWN);
}
if (!b.isEmpty() && !b.getRelative(BlockFace.UP).getType().isOccluding()) {
CompatibleParticleHandler.spawnParticles(particleType,
b.getLocation().add(.5, 1.5, .5),
0, 0, 0, 0, 1, p);
}
}
}
}
}
}

View File

@ -0,0 +1,7 @@
package com.songoda.epicanchors.utils;
import org.jetbrains.annotations.Nullable;
public interface Callback<T> {
void accept(@Nullable Exception ex, T result);
}

View File

@ -1,85 +0,0 @@
package com.songoda.epicanchors.utils;
import com.songoda.core.utils.TextUtils;
import com.songoda.core.utils.TimeUtils;
import com.songoda.epicanchors.EpicAnchors;
import com.songoda.epicanchors.settings.Settings;
import org.bukkit.*;
import org.bukkit.block.Block;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class Methods {
private static Map<String, Location> serializeCache = new HashMap<>();
public static String formatName(int ticks) {
String remaining = TimeUtils.makeReadable((ticks / 20L) * 1000L);
String name = Settings.NAMETAG.getString().replace("{REMAINING}", (ticks <= 0)
? EpicAnchors.getInstance().getLocale().getMessage("infinite").getMessage() : remaining);
return TextUtils.formatText(name);
}
/**
* Serializes the location specified.
*
* @param location The location that is to be saved.
* @return The serialized data.
*/
public static String serializeLocation(Location location) {
if (location == null)
return "";
String w = location.getWorld().getName();
double x = location.getX();
double y = location.getY();
double z = location.getZ();
String str = w + ":" + x + ":" + y + ":" + z;
str = str.replace(".0", "").replace("/", "");
return str;
}
/**
* Deserializes a location from the string.
*
* @param str The string to parse.
* @return The location that was serialized in the string.
*/
public static Location unserializeLocation(String str) {
if (str == null || str.equals(""))
return null;
if (serializeCache.containsKey(str)) {
return serializeCache.get(str).clone();
}
String cacheKey = str;
str = str.replace("y:", ":").replace("z:", ":").replace("w:", "").replace("x:", ":").replace("/", ".");
List<String> args = Arrays.asList(str.split("\\s*:\\s*"));
World world = Bukkit.getWorld(args.get(0));
double x = Double.parseDouble(args.get(1)), y = Double.parseDouble(args.get(2)), z = Double.parseDouble(args.get(3));
Location location = new Location(world, x, y, z, 0, 0);
serializeCache.put(cacheKey, location.clone());
return location;
}
public static boolean isInt(String number) {
if (number != null && !number.equals("")) {
try {
Integer.parseInt(number);
return true;
} catch (NumberFormatException var2) {
return false;
}
} else {
return false;
}
}
}

View File

@ -0,0 +1,52 @@
package com.songoda.epicanchors.utils;
import java.lang.reflect.Field;
public class ReflectionUtils {
private ReflectionUtils() {
throw new IllegalStateException("Utility class");
}
public static Object getFieldValue(Object instance, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(instance, fieldName);
boolean accessible = f.isAccessible();
f.setAccessible(true);
Object result = f.get(instance);
f.setAccessible(accessible);
return result;
}
public static void setFieldValue(Object instance, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(instance, fieldName);
boolean accessible = f.isAccessible();
f.setAccessible(true);
f.set(instance, value);
f.setAccessible(accessible);
}
private static Field getField(Object instance, String fieldName) throws NoSuchFieldException {
Field f = null;
Class<?> currClass = instance.getClass();
do {
try {
f = currClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException ex) {
currClass = currClass.getSuperclass();
if (currClass == null) {
throw ex;
}
}
} while (f == null);
return f;
}
}

View File

@ -0,0 +1,30 @@
package com.songoda.epicanchors.utils;
import java.util.concurrent.atomic.AtomicReference;
public class ThreadSync {
private final Object syncObj = new Object();
private final AtomicReference<Boolean> waiting = new AtomicReference<>(true);
public void waitForRelease() {
synchronized (syncObj) {
while (waiting.get()) {
try {
syncObj.wait();
} catch (Exception ignore) {
}
}
}
}
public void release() {
synchronized (syncObj) {
waiting.set(false);
syncObj.notifyAll();
}
}
public void reset() {
waiting.set(true);
}
}

View File

@ -0,0 +1,7 @@
package com.songoda.epicanchors.utils;
import org.jetbrains.annotations.Nullable;
public interface UpdateCallback {
void accept(@Nullable Exception ex);
}

View File

@ -0,0 +1,60 @@
package com.songoda.epicanchors.utils;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Utils {
private Utils() {
throw new IllegalStateException("Utility class");
}
public static boolean isInt(String s) {
if (s != null && !s.isEmpty()) {
try {
Integer.parseInt(s);
return true;
} catch (NumberFormatException ignore) {
}
}
return false;
}
/**
* Get all values of a String[] which start with a given String
*
* @param value The String to search for
* @param list The array to iterate
*
* @return A list with all the matches
*/
public static List<String> getMatches(String value, Collection<String> list, boolean caseInsensitive) {
List<String> result = new LinkedList<>();
for (String str : list) {
if (str.startsWith(value.toLowerCase())
|| (caseInsensitive && str.toLowerCase().startsWith(value.toLowerCase()))) {
result.add(str);
}
}
return result;
}
public static void logException(@Nullable Plugin plugin, @NotNull Throwable th) {
logException(plugin, th, null);
}
public static void logException(@Nullable Plugin plugin, @NotNull Throwable th, @Nullable String type) {
Logger logger = plugin != null ? plugin.getLogger() : Logger.getGlobal();
logger.log(Level.FINER, th, () -> "A " + (type == null ? "critical" : type) + " error occurred");
}
}

View File

@ -0,0 +1,66 @@
package com.songoda.epicanchors.utils;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings("JavaReflectionMemberAccess")
public class WorldUtils {
private WorldUtils() {
throw new IllegalStateException("Utility class");
}
private static final Method addChunkTicketMethod;
private static final Method removeChunkTicketMethod;
static {
Method tmpAdd;
Method tmpRem;
try {
tmpAdd = Chunk.class.getDeclaredMethod("addPluginChunkTicket", Plugin.class);
tmpRem = Chunk.class.getDeclaredMethod("removePluginChunkTicket", Plugin.class);
} catch (NoSuchMethodException ignore) {
tmpAdd = null;
tmpRem = null;
}
addChunkTicketMethod = tmpAdd;
removeChunkTicketMethod = tmpRem;
}
public static int getRandomTickSpeed(World world) {
try {
return Integer.parseInt(world.getGameRuleValue("randomTickSpeed"));
} catch (NumberFormatException ignore) {
return 3;
}
}
public static boolean loadAnchoredChunk(Chunk chunk, Plugin plugin) {
if (addChunkTicketMethod != null) {
try {
return (boolean) addChunkTicketMethod.invoke(chunk, plugin);
} catch (IllegalAccessException | InvocationTargetException ex) {
Utils.logException(plugin, ex);
}
}
return chunk.load();
}
public static boolean unloadAnchoredChunk(Chunk chunk, Plugin plugin) {
if (removeChunkTicketMethod != null) {
try {
removeChunkTicketMethod.invoke(chunk, plugin);
} catch (IllegalAccessException | InvocationTargetException ex) {
Utils.logException(plugin, ex);
}
}
return chunk.unload();
}
}

View File

@ -14,9 +14,16 @@ interface:
addtimewithxplore: '&7Cost: &a%cost% Levels'
addtimewitheconomy: '&aAdd 30 Minutes with ECO'
addtimewitheconomylore: '&7Cost: &a$%cost%'
cancelDestroy: '&4Cancel'
cancelDestroyLore: ''
confirmDestroy: '&2Confirm'
confirmDestroyLore: '&aA new anchor with the remaining time will be dropped.'
confirmDestroyLoreNoDrops: '&4The remaining time on this anchor will be lost!'
infinite: '&cCannot upgrade an infinite anchor!'
anchor:
title: ChunkAnchor
title: 'ChunkAnchor'
smalltitle: '&eChunkAnchor'
# Command Messages

View File

@ -1,13 +1,29 @@
name: EpicAnchors
description: EpicAnchors
version: maven-version-number
softdepend: [EpicSpawners, Towny, RedProtect, Kingdoms, PlotsSquared, GriefPrevention, USkyBlock, ASkyBlock, WorldGuard, Factions, Vault, HolographicDisplays, Holograms, CMI]
main: com.songoda.epicanchors.EpicAnchors
author: songoda
name: ${project.name}
description: ${project.description}
version: ${project.version}
api-version: 1.13
main: com.songoda.epicanchors.EpicAnchors
softdepend:
- Holograms
- HolographicDisplays
- Vault
author: Songoda
authors: [ SpraxDev ]
website: ${project.url}
commands:
EpicAnchors:
description: I have no idea.
aliases:
- ea
permissions:
EpicAnchors.cmd.show:
default: true
aliases: [ea]
usage: /<command> [reload]
EpicAnchors.admin:
children:
EpicAnchors.cmd.reload: true
EpicAnchors.cmd.settings: true
EpicAnchors.cmd.give: true