diff --git a/LICENSE b/LICENSE index d3087e4..0e5c92e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,277 +1,277 @@ -Eclipse Public License - v 2.0 - - THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE - PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION - OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - - a) in the case of the initial Contributor, the initial content - Distributed under this Agreement, and - - b) in the case of each subsequent Contributor: - i) changes to the Program, and - ii) additions to the Program; - where such changes and/or additions to the Program originate from - and are Distributed by that particular Contributor. A Contribution - "originates" from a Contributor if it was added to the Program by - such Contributor itself or anyone acting on such Contributor's behalf. - Contributions do not include changes or additions to the Program that - are not Modified Works. - -"Contributor" means any person or entity that Distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions Distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement -or any Secondary License (as applicable), including Contributors. - -"Derivative Works" shall mean any work, whether in Source Code or other -form, that is based on (or derived from) the Program and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. - -"Modified Works" shall mean any work in Source Code or other form that -results from an addition to, deletion from, or modification of the -contents of the Program, including, for purposes of clarity any new file -in Source Code form that contains any contents of the Program. Modified -Works shall not include works that contain only declarations, -interfaces, types, classes, structures, or files of the Program solely -in each case in order to link to, bind by name, or subclass the Program -or Modified Works thereof. - -"Distribute" means the acts of a) distributing or b) making available -in any manner that enables the transfer of a copy. - -"Source Code" means the form of a Program preferred for making -modifications, including but not limited to software source code, -documentation source, and configuration files. - -"Secondary License" means either the GNU General Public License, -Version 2.0, or any later versions of that license, including any -exceptions or additional permissions as identified by the initial -Contributor. - -2. GRANT OF RIGHTS - - a) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free copyright - license to reproduce, prepare Derivative Works of, publicly display, - publicly perform, Distribute and sublicense the Contribution of such - Contributor, if any, and such Derivative Works. - - b) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free patent - license under Licensed Patents to make, use, sell, offer to sell, - import and otherwise transfer the Contribution of such Contributor, - if any, in Source Code or other form. This patent license shall - apply to the combination of the Contribution and the Program if, at - the time the Contribution is added by the Contributor, such addition - of the Contribution causes such combination to be covered by the - Licensed Patents. The patent license shall not apply to any other - combinations which include the Contribution. No hardware per se is - licensed hereunder. - - c) Recipient understands that although each Contributor grants the - licenses to its Contributions set forth herein, no assurances are - provided by any Contributor that the Program does not infringe the - patent or other intellectual property rights of any other entity. - Each Contributor disclaims any liability to Recipient for claims - brought by any other entity based on infringement of intellectual - property rights or otherwise. As a condition to exercising the - rights and licenses granted hereunder, each Recipient hereby - assumes sole responsibility to secure any other intellectual - property rights needed, if any. For example, if a third party - patent license is required to allow Recipient to Distribute the - Program, it is Recipient's responsibility to acquire that license - before distributing the Program. - - d) Each Contributor represents that to its knowledge it has - sufficient copyright rights in its Contribution, if any, to grant - the copyright license set forth in this Agreement. - - e) Notwithstanding the terms of any Secondary License, no - Contributor makes additional grants to any Recipient (other than - those set forth in this Agreement) as a result of such Recipient's - receipt of the Program under the terms of a Secondary License - (if permitted under the terms of Section 3). - -3. REQUIREMENTS - -3.1 If a Contributor Distributes the Program in any form, then: - - a) the Program must also be made available as Source Code, in - accordance with section 3.2, and the Contributor must accompany - the Program with a statement that the Source Code for the Program - is available under this Agreement, and informs Recipients how to - obtain it in a reasonable manner on or through a medium customarily - used for software exchange; and - - b) the Contributor may Distribute the Program under a license - different than this Agreement, provided that such license: - i) effectively disclaims on behalf of all other Contributors all - warranties and conditions, express and implied, including - warranties or conditions of title and non-infringement, and - implied warranties or conditions of merchantability and fitness - for a particular purpose; - - ii) effectively excludes on behalf of all other Contributors all - liability for damages, including direct, indirect, special, - incidental and consequential damages, such as lost profits; - - iii) does not attempt to limit or alter the recipients' rights - in the Source Code under section 3.2; and - - iv) requires any subsequent distribution of the Program by any - party to be under a license that satisfies the requirements - of this section 3. - -3.2 When the Program is Distributed as Source Code: - - a) it must be made available under this Agreement, or if the - Program (i) is combined with other material in a separate file or - files made available under a Secondary License, and (ii) the initial - Contributor attached to the Source Code the notice described in - Exhibit A of this Agreement, then the Program may be made available - under the terms of such Secondary Licenses, and - - b) a copy of this Agreement must be included with each copy of - the Program. - -3.3 Contributors may not remove or alter any copyright, patent, -trademark, attribution notices, disclaimers of warranty, or limitations -of liability ("notices") contained within the Program from any copy of -the Program which they Distribute, provided that Contributors may add -their own appropriate notices. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, -the Contributor who includes the Program in a commercial product -offering should do so in a manner which does not create potential -liability for other Contributors. Therefore, if a Contributor includes -the Program in a commercial product offering, such Contributor -("Commercial Contributor") hereby agrees to defend and indemnify every -other Contributor ("Indemnified Contributor") against any losses, -damages and costs (collectively "Losses") arising from claims, lawsuits -and other legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the Program -in a commercial product offering. The obligations in this section do not -apply to any claims or Losses relating to any actual or alleged -intellectual property infringement. In order to qualify, an Indemnified -Contributor must: a) promptly notify the Commercial Contributor in -writing of such claim, and b) allow the Commercial Contributor to control, -and cooperate with the Commercial Contributor in, the defense and any -related settlement negotiations. The Indemnified Contributor may -participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those performance -claims and warranties, and if a court requires any other Contributor to -pay any damages as a result, the Commercial Contributor must pay -those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" -BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR -IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF -TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR -PURPOSE. Each Recipient is solely responsible for determining the -appropriateness of using and distributing the Program and assumes all -risks associated with its exercise of rights under this Agreement, -including but not limited to the risks and costs of program errors, -compliance with applicable laws, damage to or loss of data, programs -or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS -SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST -PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE -EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further -action by the parties hereto, such provision shall be reformed to the -minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity -(including a cross-claim or counterclaim in a lawsuit) alleging that the -Program itself (excluding combinations of the Program with other software -or hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it -fails to comply with any of the material terms or conditions of this -Agreement and does not cure such failure in a reasonable period of -time after becoming aware of such noncompliance. If all Recipient's -rights under this Agreement terminate, Recipient agrees to cease use -and distribution of the Program as soon as reasonably practicable. -However, Recipient's obligations under this Agreement and any licenses -granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and -may only be modified in the following manner. The Agreement Steward -reserves the right to publish new versions (including revisions) of -this Agreement from time to time. No one other than the Agreement -Steward has the right to modify this Agreement. The Eclipse Foundation -is the initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -Distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is published, -Contributor may elect to Distribute the Program (including its -Contributions) under the new version. - -Except as expressly stated in Sections 2(a) and 2(b) above, Recipient -receives no rights or licenses to the intellectual property of any -Contributor under this Agreement, whether expressly, by implication, -estoppel or otherwise. All rights in the Program not expressly granted -under this Agreement are reserved. Nothing in this Agreement is intended -to be enforceable by any entity that is not a Contributor or Recipient. -No third-party beneficiary rights are created under this Agreement. - -Exhibit A - Form of Secondary Licenses Notice - -"This Source Code may also be made available under the following -Secondary Licenses when the conditions for such availability set forth -in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), -version(s), and exceptions or additional permissions here}." - - Simply including a copy of this Agreement, including this Exhibit A - is not sufficient to license the Source Code under Secondary Licenses. - - If it is not possible or desirable to put the notice in a particular - file, then You may include the notice in a location (such as a LICENSE - file in a relevant directory) where a recipient would be likely to - look for such a notice. - - You may add additional accurate notices of copyright ownership. +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/README.md b/README.md index 70d12c0..69231dd 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,83 @@ -# Limits -[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Limits)](https://ci.codemc.org/job/BentoBoxWorld/job/Limits/) - -Add-on for BentoBox to limit island blocks and entities in GameModes like BSkyBlock and AcidIsland. This add-on will work -in any game mode world. - -## How to use - -1. Place the Limits addon jar in the addons folder of the BentoBox plugin -2. Restart the server -3. The addon will create a data folder and inside the folder will be a config.yml -4. Edit the config.yml how you want. -5. Restart the server if you make a change - -## Commands -There is a user command and an admin command called "limits". Admins can check the limits of a specific island owner. Both show a GUI panel with the limits and the current count. - -## Setup - Config.yml - -The config.yml has the following sections: - -* blocklimits -* worlds -* entitylimits - -### blocklimits - -This section lists the maximum number of blocks allowed for each block material. Do not use non-block materials because they will not work. The limits apply to all game worlds. - -### worlds - -This section lists block limits for specific worlds. You must name the world specifically, e.g. AcidIsland_world and then list the materials and the limit. - -### entitylimits - -Coming soon! - -## Permissions - -Island owners can have exclusive permissions that override the default or world settings. The format is: - -Format is `GAME-MODE-NAME.island.limit.MATERIAL.LIMIT` - -example: `bskyblock.island.limit.hopper.10` - -Permissions activate when the player logs in. - -Usage permissions are (put the gamemode name, e.g. acidisland at the front): - -``` - GAMEMODE_NAME.limits.player.limits: - description: Player can use limits command - default: true - GAMEMODE_NAME.limits.admin.limits: - description: Player can use admin limits command - default: op -``` - -## Items that cannot be limited -Some items cannot be limited (right now). The reasons are usually because there are too many ways to remove the item without it being tracked. If you are a programmer and can work out how to fix these, then please submit a PR! - -* Primed TNT -* Evoker Fangs -* Llama Spit -* Dragon Fireball -* Area Effect Cloud -* Ender signal -* Small fireball -* Fireball -* Thrown Exp Bottle -* Shulker Bullet -* Wither Skull -* Tridents -* Arrows -* Spectral Arrows -* Snowballs -* Eggs -* Leashes -* Ender crystals -* Ender pearls -* Ender dragon -* Item frames -* Paintings +# Limits +[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Limits)](https://ci.codemc.org/job/BentoBoxWorld/job/Limits/) + +Add-on for BentoBox to limit island blocks and entities in GameModes like BSkyBlock and AcidIsland. This add-on will work +in any game mode world. + +## How to use + +1. Place the Limits addon jar in the addons folder of the BentoBox plugin +2. Restart the server +3. The addon will create a data folder and inside the folder will be a config.yml +4. Edit the config.yml how you want. +5. Restart the server if you make a change + +## Commands +There is a user command and an admin command called "limits". Admins can check the limits of a specific island owner. Both show a GUI panel with the limits and the current count. + +## Setup - Config.yml + +The config.yml has the following sections: + +* blocklimits +* worlds +* entitylimits + +### blocklimits + +This section lists the maximum number of blocks allowed for each block material. Do not use non-block materials because they will not work. The limits apply to all game worlds. + +### worlds + +This section lists block limits for specific worlds. You must name the world specifically, e.g. AcidIsland_world and then list the materials and the limit. + +### entitylimits + +Coming soon! + +## Permissions + +Island owners can have exclusive permissions that override the default or world settings. The format is: + +Format is `GAME-MODE-NAME.island.limit.MATERIAL.LIMIT` + +example: `bskyblock.island.limit.hopper.10` + +Permissions activate when the player logs in. + +Usage permissions are (put the gamemode name, e.g. acidisland at the front): + +``` + GAMEMODE_NAME.limits.player.limits: + description: Player can use limits command + default: true + GAMEMODE_NAME.limits.admin.limits: + description: Player can use admin limits command + default: op +``` + +## Items that cannot be limited +Some items cannot be limited (right now). The reasons are usually because there are too many ways to remove the item without it being tracked. If you are a programmer and can work out how to fix these, then please submit a PR! + +* Primed TNT +* Evoker Fangs +* Llama Spit +* Dragon Fireball +* Area Effect Cloud +* Ender signal +* Small fireball +* Fireball +* Thrown Exp Bottle +* Shulker Bullet +* Wither Skull +* Tridents +* Arrows +* Spectral Arrows +* Snowballs +* Eggs +* Leashes +* Ender crystals +* Ender pearls +* Ender dragon +* Item frames +* Paintings diff --git a/pom.xml b/pom.xml index 1f58841..daad7a9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,292 +1,292 @@ - - 4.0.0 - world.bentobox - limits - Limits - ${revision} - - An add-on for BentoBox that limits blocks and entities on islands. - https://github.com/BentoBoxWorld/Limits - 2018 - - - - tastybento - tastybento@bentobox.world - -8 - - Lead Developer - - - - - - scm:git:https://github.com/BentoBoxWorld/Limits.git - scm:git:git@github.com:BentoBoxWorld/Limits.git - https://github.com/BentoBoxWorld/Limits - - - - jenkins - http://ci.codemc.org/job/BentoBoxWorld/job/Limits - - - - GitHub - https://github.com/BentoBoxWorld/Limits/issues - - - - - bentoboxworld - https://repo.codemc.org/repository/bentoboxworld/ - - - - - - UTF-8 - UTF-8 - 17 - - 2.0.9 - - 1.21.3-R0.1-SNAPSHOT - 2.7.1-SNAPSHOT - - ${build.version}-SNAPSHOT - - -LOCAL - - 1.26.0 - BentoBoxWorld_Limits - bentobox-world - https://sonarcloud.io - - - - - - - - ci - - - env.BUILD_NUMBER - - - - - -b${env.BUILD_NUMBER} - - - - - - - - master - - - env.GIT_BRANCH - origin/master - - - - - ${build.version} - - - - - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots - - - bentoboxworld - https://repo.codemc.org/repository/bentoboxworld/ - - - codemc - https://repo.codemc.org/repository/maven-snapshots/ - - - - - - - org.spigotmc - spigot-api - ${spigot.version} - provided - - - - org.mockito - mockito-core - 3.11.1 - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - org.powermock - powermock-api-mockito2 - ${powermock.version} - test - - - world.bentobox - bentobox - ${bentobox.version} - provided - - - - - - - - - - - ${project.name}-${revision}${build.number} - - clean package - - - src/main/resources - true - - - src/main/resources/locales - ./locales - false - - - - - org.apache.maven.plugins - maven-clean-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-resources-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - - ${argLine} - --add-opens java.base/java.lang=ALL-UNNAMED - --add-opens java.base/java.math=ALL-UNNAMED - --add-opens java.base/java.io=ALL-UNNAMED - --add-opens java.base/java.util=ALL-UNNAMED - --add-opens - java.base/java.util.stream=ALL-UNNAMED - --add-opens java.base/java.text=ALL-UNNAMED - --add-opens - java.base/java.util.regex=ALL-UNNAMED - --add-opens - java.base/java.nio.channels.spi=ALL-UNNAMED - --add-opens java.base/sun.nio.ch=ALL-UNNAMED - --add-opens java.base/java.net=ALL-UNNAMED - --add-opens - java.base/java.util.concurrent=ALL-UNNAMED - --add-opens java.base/sun.nio.fs=ALL-UNNAMED - --add-opens java.base/sun.nio.cs=ALL-UNNAMED - --add-opens java.base/java.nio.file=ALL-UNNAMED - --add-opens - java.base/java.nio.charset=ALL-UNNAMED - --add-opens - java.base/java.lang.reflect=ALL-UNNAMED - --add-opens - java.logging/java.util.logging=ALL-UNNAMED - --add-opens java.base/java.lang.ref=ALL-UNNAMED - --add-opens java.base/java.util.jar=ALL-UNNAMED - --add-opens java.base/java.util.zip=ALL-UNNAMED - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-install-plugin - 2.5.2 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - org.jacoco - jacoco-maven-plugin - 0.8.10 - - true - - - **/*Names* - - org/bukkit/Material* - - - - - prepare-agent - - prepare-agent - - - - report - - report - - - - XML - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${java.version} - - - - - - + + 4.0.0 + world.bentobox + limits + Limits + ${revision} + + An add-on for BentoBox that limits blocks and entities on islands. + https://github.com/BentoBoxWorld/Limits + 2018 + + + + tastybento + tastybento@bentobox.world + -8 + + Lead Developer + + + + + + scm:git:https://github.com/BentoBoxWorld/Limits.git + scm:git:git@github.com:BentoBoxWorld/Limits.git + https://github.com/BentoBoxWorld/Limits + + + + jenkins + http://ci.codemc.org/job/BentoBoxWorld/job/Limits + + + + GitHub + https://github.com/BentoBoxWorld/Limits/issues + + + + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + + + + + + UTF-8 + UTF-8 + 17 + + 2.0.9 + + 1.21.3-R0.1-SNAPSHOT + 2.7.1-SNAPSHOT + + ${build.version}-SNAPSHOT + + -LOCAL + + 1.27.0 + BentoBoxWorld_Limits + bentobox-world + https://sonarcloud.io + + + + + + + + ci + + + env.BUILD_NUMBER + + + + + -b${env.BUILD_NUMBER} + + + + + + + + master + + + env.GIT_BRANCH + origin/master + + + + + ${build.version} + + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots + + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + + + codemc + https://repo.codemc.org/repository/maven-snapshots/ + + + + + + + org.spigotmc + spigot-api + ${spigot.version} + provided + + + + org.mockito + mockito-core + 3.11.1 + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito2 + ${powermock.version} + test + + + world.bentobox + bentobox + ${bentobox.version} + provided + + + + + + + + + + + ${project.name}-${revision}${build.number} + + clean package + + + src/main/resources + true + + + src/main/resources/locales + ./locales + false + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + ${argLine} + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.math=ALL-UNNAMED + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens + java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.text=ALL-UNNAMED + --add-opens + java.base/java.util.regex=ALL-UNNAMED + --add-opens + java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens + java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/sun.nio.fs=ALL-UNNAMED + --add-opens java.base/sun.nio.cs=ALL-UNNAMED + --add-opens java.base/java.nio.file=ALL-UNNAMED + --add-opens + java.base/java.nio.charset=ALL-UNNAMED + --add-opens + java.base/java.lang.reflect=ALL-UNNAMED + --add-opens + java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.lang.ref=ALL-UNNAMED + --add-opens java.base/java.util.jar=ALL-UNNAMED + --add-opens java.base/java.util.zip=ALL-UNNAMED + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + true + + + **/*Names* + + org/bukkit/Material* + + + + + prepare-agent + + prepare-agent + + + + report + + report + + + + XML + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${java.version} + + + + + + diff --git a/src/main/java/world/bentobox/limits/EntityGroup.java b/src/main/java/world/bentobox/limits/EntityGroup.java index 7f077f8..f94e869 100644 --- a/src/main/java/world/bentobox/limits/EntityGroup.java +++ b/src/main/java/world/bentobox/limits/EntityGroup.java @@ -1,72 +1,72 @@ -package world.bentobox.limits; - -import java.util.Objects; -import java.util.Set; - -import org.bukkit.Material; -import org.bukkit.entity.EntityType; - -/** - * A named class representing a group of entities and their limits - * - */ -public class EntityGroup { - private final String name; - private final Set types; - private final int limit; - private final Material icon; - - public EntityGroup(String name, Set types, int limit, Material icon) { - this.name = name; - this.types = types; - this.limit = limit; - this.icon = icon; - } - - public boolean contains(EntityType type) { - return types.contains(type); - } - - public String getName() { - return name; - } - - public Set getTypes() { - return types; - } - - public int getLimit() { - return limit; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 83 * hash + Objects.hashCode(this.name); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final EntityGroup other = (EntityGroup) obj; - return Objects.equals(this.name, other.name); - } - - /** - * @return the icon - */ - public Material getIcon() { - return Objects.requireNonNullElse(icon, Material.BARRIER); - } - - @Override - public String toString() { - return "EntityGroup [name=" + name + ", types=" + types + ", limit=" + limit + ", icon=" + icon + "]"; - } +package world.bentobox.limits; + +import java.util.Objects; +import java.util.Set; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +/** + * A named class representing a group of entities and their limits + * + */ +public class EntityGroup { + private final String name; + private final Set types; + private final int limit; + private final Material icon; + + public EntityGroup(String name, Set types, int limit, Material icon) { + this.name = name; + this.types = types; + this.limit = limit; + this.icon = icon; + } + + public boolean contains(EntityType type) { + return types.contains(type); + } + + public String getName() { + return name; + } + + public Set getTypes() { + return types; + } + + public int getLimit() { + return limit; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(this.name); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final EntityGroup other = (EntityGroup) obj; + return Objects.equals(this.name, other.name); + } + + /** + * @return the icon + */ + public Material getIcon() { + return Objects.requireNonNullElse(icon, Material.BARRIER); + } + + @Override + public String toString() { + return "EntityGroup [name=" + name + ", types=" + types + ", limit=" + limit + ", icon=" + icon + "]"; + } } \ No newline at end of file diff --git a/src/main/java/world/bentobox/limits/Limits.java b/src/main/java/world/bentobox/limits/Limits.java index 94bc6e7..5c8dc99 100644 --- a/src/main/java/world/bentobox/limits/Limits.java +++ b/src/main/java/world/bentobox/limits/Limits.java @@ -1,267 +1,300 @@ -package world.bentobox.limits; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.bukkit.Material; -import org.bukkit.Registry; -import org.bukkit.World; -import org.bukkit.entity.EntityType; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.addons.Addon; -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.commands.admin.AdminCommand; -import world.bentobox.limits.commands.player.PlayerCommand; -import world.bentobox.limits.listeners.BlockLimitsListener; -import world.bentobox.limits.listeners.EntityLimitListener; -import world.bentobox.limits.listeners.JoinListener; -import world.bentobox.limits.objects.IslandBlockCount; - - -/** - * Addon to BentoBox that monitors and enforces limits - * @author tastybento - * - */ -public class Limits extends Addon { - - private static final String LIMIT_NOT_SET = "Limit not set"; - private Settings settings; - private List gameModes = new ArrayList<>(); - private BlockLimitsListener blockLimitListener; - private JoinListener joinListener; - - @Override - public void onDisable(){ - if (blockLimitListener != null) { - blockLimitListener.save(); - } - } - - @Override - public void onEnable() { - // Load the plugin's config - saveDefaultConfig(); - // Load settings - settings = new Settings(this); - // Register worlds from GameModes - gameModes = getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> settings.getGameModes().contains(gm.getDescription().getName())) - .collect(Collectors.toList()); - gameModes.forEach(gm -> - { - // Register commands - gm.getAdminCommand().ifPresent(a -> new AdminCommand(this, a)); - gm.getPlayerCommand().ifPresent(a -> new PlayerCommand(this, a)); - registerPlaceholders(gm); - log("Limits will apply to " + gm.getDescription().getName()); - } - ); - // Register listener - blockLimitListener = new BlockLimitsListener(this); - registerListener(blockLimitListener); - joinListener = new JoinListener(this); - registerListener(joinListener); - registerListener(new EntityLimitListener(this)); - // Done - } - - /** - * @return the settings - */ - public Settings getSettings() { - return settings; - } - - /** - * @return the gameModes - */ - public List getGameModes() { - return gameModes; - } - - /** - * @return the blockLimitListener - */ - public BlockLimitsListener getBlockLimitListener() { - return blockLimitListener; - } - - /** - * Checks if this world is covered by the activated game modes - * @param world - world - * @return true or false - */ - public boolean inGameModeWorld(World world) { - return gameModes.stream().anyMatch(gm -> gm.inWorld(world)); - } - - /** - * Get the name of the game mode for this world - * @param world - world - * @return game mode name or empty string if none - */ - public String getGameModeName(World world) { - return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(gm -> gm.getDescription().getName()).orElse(""); - } - - /** - * Get the permission prefix for this world - * @param world - world - * @return permisdsion prefix or empty string if none - */ - public String getGameModePermPrefix(World world) { - return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(GameModeAddon::getPermissionPrefix).orElse(""); - } - - - /** - * Check if any of the game modes covered have this name - * @param gameMode - name of game mode - * @return true or false - */ - public boolean isCoveredGameMode(String gameMode) { - return gameModes.stream().anyMatch(gm -> gm.getDescription().getName().equals(gameMode)); - } - - /** - * @return the joinListener - */ - public JoinListener getJoinListener() { - return joinListener; - } - - private void registerPlaceholders(GameModeAddon gm) { - if (getPlugin().getPlaceholdersManager() == null) return; - Registry.MATERIAL.stream() - .filter(Material::isBlock) - .forEach(m -> registerCountAndLimitPlaceholders(m, gm)); - - Arrays.stream(EntityType.values()) - .forEach(e -> registerCountAndLimitPlaceholders(e, gm)); - } - - /** - * Registers placeholders for the count and limit of the material - * in the format of %Limits_(gamemode prefix)_island_(lowercase material name)_count% - * and %Limits_(gamemode prefix)_island_(lowercase material name)_limit% - * - * Example: registerCountAndLimitPlaceholders("HOPPER", gm); - * Placeholders: - * "Limits_bskyblock_island_hopper_count" - * "Limits_bskyblock_island_hopper_limit" - * "Limits_bskyblock_island_hopper_base_limit" - * "Limits_bskyblock_island_zombie_limit" - * - * @param m material - * @param gm game mode - */ - private void registerCountAndLimitPlaceholders(Material m, GameModeAddon gm) { - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_count", - user -> String.valueOf(getCount(user, m, gm))); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_limit", - user -> getLimit(user, m, gm)); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_base_limit", - user -> getBaseLimit(user, m, gm)); - } - - private void registerCountAndLimitPlaceholders(EntityType e, GameModeAddon gm) { - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_limit", - user -> getLimit(user, e, gm)); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_base_limit", - user -> getBaseLimit(user, e, gm)); - } - - /** - * @param user - Used to identify the island the user belongs to - * @param m - The material we are trying to count on the island - * @param gm Game Mode Addon - * @return Number of blocks of the specified material on the given user's island - */ - private int getCount(@Nullable User user, Material m, GameModeAddon gm) { - Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); - if (is == null) { - return 0; - } - @Nullable IslandBlockCount ibc = getBlockLimitListener().getIsland(is.getUniqueId()); - if (ibc == null) { - return 0; - } - return ibc.getBlockCount(m); - } - - /** - * @param user - Used to identify the island the user belongs to - * @param m - The material whose limit we are querying - * @param gm Game Mode Addon - * @return The limit of the specified material on the given user's island - */ - private String getLimit(@Nullable User user, Material m, GameModeAddon gm) { - Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); - if (is == null) { - return LIMIT_NOT_SET; - } - if (user != null) { - // Check the permissions of the user and update - this.getJoinListener().checkPerms(user.getPlayer(), gm.getPermissionPrefix() + "island.limit.", - is.getUniqueId(), gm.getDescription().getName()); - } - int limit = this.getBlockLimitListener(). - getMaterialLimits(is.getWorld(), is.getUniqueId()).getOrDefault(m, -1); - - return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); - } - - private String getBaseLimit(@Nullable User user, Material m, GameModeAddon gm) { - Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); - if (is == null) { - return LIMIT_NOT_SET; - } - - int limit = this.getBlockLimitListener(). - getMaterialLimits(is.getWorld(), is.getUniqueId()). - getOrDefault(m, -1); - - if (limit > 0) { - limit -= this.getBlockLimitListener().getIsland(is).getBlockLimitOffset(m); - } - - return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); - } - - private String getLimit(@Nullable User user, EntityType e, GameModeAddon gm) { - Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); - if (is == null) { - return LIMIT_NOT_SET; - } - - int limit = this.getBlockLimitListener().getIsland(is).getEntityLimit(e); - if (limit < 0 && this.getSettings().getLimits().containsKey(e)) { - limit = this.getSettings().getLimits().get(e); - } - - return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); - } - - private String getBaseLimit(@Nullable User user, EntityType e, GameModeAddon gm) { - Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); - if (is == null || !this.getSettings().getLimits().containsKey(e)) { - return LIMIT_NOT_SET; - } - - int limit = this.getSettings().getLimits().get(e); - - return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); - } - -} +package world.bentobox.limits; + +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.limits.commands.admin.AdminCommand; +import world.bentobox.limits.commands.player.PlayerCommand; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.listeners.EntityLimitListener; +import world.bentobox.limits.listeners.JoinListener; +import world.bentobox.limits.objects.IslandBlockCount; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * Addon to BentoBox that monitors and enforces limits + * + * @author tastybento + */ +public class Limits extends Addon { + + private static final String LIMIT_NOT_SET = "Limit not set"; + private Settings settings; + private List gameModes = new ArrayList<>(); + private BlockLimitsListener blockLimitListener; + private JoinListener joinListener; + private IslandWorldManager islandWorldManager; + + @Override + public void onDisable() { + if (blockLimitListener != null) { + blockLimitListener.save(); + } + } + + @Override + public void onEnable() { + // Load the plugin's config + saveDefaultConfig(); + this.islandWorldManager = getPlugin().getIWM(); + // Load settings + settings = new Settings(this); + // Register worlds from GameModes + gameModes = getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> settings.getGameModes().contains(gm.getDescription().getName())) + .collect(Collectors.toList()); + gameModes.forEach(gm -> + { + // Register commands + gm.getAdminCommand().ifPresent(a -> new AdminCommand(this, a)); + gm.getPlayerCommand().ifPresent(a -> new PlayerCommand(this, a)); + registerPlaceholders(gm); + log("Limits will apply to " + gm.getDescription().getName()); + } + ); + // Register listener + blockLimitListener = new BlockLimitsListener(this); + registerListener(blockLimitListener); + joinListener = new JoinListener(this); + registerListener(joinListener); + registerListener(new EntityLimitListener(this)); + // Done + } + + /** + * @return the settings + */ + public Settings getSettings() { + return settings; + } + + /** + * @return the gameModes + */ + public List getGameModes() { + return gameModes; + } + + /** + * @return the blockLimitListener + */ + public BlockLimitsListener getBlockLimitListener() { + return blockLimitListener; + } + + /** + * Checks if this world is covered by the activated game modes + * + * @param world - world + * @return true or false + */ + public boolean inGameModeWorld(World world) { + return gameModes.stream().anyMatch(gm -> gm.inWorld(world)); + } + + /** + * Get the name of the game mode for this world + * + * @param world - world + * @return game mode name or empty string if none + */ + public String getGameModeName(World world) { + return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(gm -> gm.getDescription().getName()).orElse(""); + } + + /** + * Get the permission prefix for this world + * + * @param world - world + * @return permisdsion prefix or empty string if none + */ + public String getGameModePermPrefix(World world) { + return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(GameModeAddon::getPermissionPrefix).orElse(""); + } + + + /** + * Check if any of the game modes covered have this name + * + * @param gameMode - name of game mode + * @return true or false + */ + public boolean isCoveredGameMode(String gameMode) { + return gameModes.stream().anyMatch(gm -> gm.getDescription().getName().equals(gameMode)); + } + + /** + * @return the joinListener + */ + public JoinListener getJoinListener() { + return joinListener; + } + + private void registerPlaceholders(GameModeAddon gm) { + if (getPlugin().getPlaceholdersManager() == null) return; + Registry.MATERIAL.stream() + .filter(Material::isBlock) + .forEach(m -> registerCountAndLimitPlaceholders(m, gm)); + + Arrays.stream(EntityType.values()) + .forEach(e -> registerCountAndLimitPlaceholders(e, gm)); + } + + /** + * Registers placeholders for the count and limit of the material + * in the format of %Limits_(gamemode prefix)_island_(lowercase material name)_count% + * and %Limits_(gamemode prefix)_island_(lowercase material name)_limit% + *

+ * Example: registerCountAndLimitPlaceholders("HOPPER", gm); + * Placeholders: + * "Limits_bskyblock_island_hopper_count" + * "Limits_bskyblock_island_hopper_limit" + * "Limits_bskyblock_island_hopper_base_limit" + * "Limits_bskyblock_island_zombie_limit" + * + * @param m material + * @param gm game mode + */ + private void registerCountAndLimitPlaceholders(Material m, GameModeAddon gm) { + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_count", + user -> String.valueOf(getCount(user, m, gm))); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_limit", + user -> getLimit(user, m, gm)); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_base_limit", + user -> getBaseLimit(user, m, gm)); + } + + private void registerCountAndLimitPlaceholders(EntityType e, GameModeAddon gm) { + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_limit", + user -> getLimit(user, e, gm)); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_base_limit", + user -> getBaseLimit(user, e, gm)); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_count", + user -> String.valueOf(getCount(user, e, gm))); + } + + /** + * @param user - Used to identify the island the user belongs to + * @param m - The material we are trying to count on the island + * @param gm Game Mode Addon + * @return Number of blocks of the specified material on the given user's island + */ + private int getCount(@Nullable User user, Material m, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null) { + return 0; + } + @Nullable IslandBlockCount ibc = getBlockLimitListener().getIsland(is.getUniqueId()); + if (ibc == null) { + return 0; + } + return ibc.getBlockCount(m); + } + + private long getCount(@Nullable User user, EntityType e, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null || e.getEntityClass() == null) { + return 0; + } + Class entityClass = e.getEntityClass(); + long count = is.getWorld().getEntitiesByClass(entityClass).stream() + .filter(ent -> is.inIslandSpace(ent.getLocation())).count(); + /* NETHER */ + if (islandWorldManager.isNetherIslands(is.getWorld()) && islandWorldManager.getNetherWorld(is.getWorld()) != null) { + count += islandWorldManager.getNetherWorld(is.getWorld()).getEntitiesByClass(entityClass).stream() + .filter(ent -> is.inIslandSpace(ent.getLocation())).count(); + } + /* END */ + if (islandWorldManager.isEndIslands(is.getWorld()) && islandWorldManager.getEndWorld(is.getWorld()) != null) { + count += islandWorldManager.getEndWorld(is.getWorld()).getEntitiesByClass(entityClass).stream() + .filter(ent -> is.inIslandSpace(ent.getLocation())).count(); + } + return count; + } + + + /** + * @param user - Used to identify the island the user belongs to + * @param m - The material whose limit we are querying + * @param gm Game Mode Addon + * @return The limit of the specified material on the given user's island + */ + private String getLimit(@Nullable User user, Material m, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null) { + return LIMIT_NOT_SET; + } + if (user != null) { + // Check the permissions of the user and update + this.getJoinListener().checkPerms(user.getPlayer(), gm.getPermissionPrefix() + "island.limit.", + is.getUniqueId(), gm.getDescription().getName()); + } + int limit = this.getBlockLimitListener(). + getMaterialLimits(is.getWorld(), is.getUniqueId()).getOrDefault(m, -1); + + return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); + } + + private String getBaseLimit(@Nullable User user, Material m, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null) { + return LIMIT_NOT_SET; + } + + int limit = this.getBlockLimitListener(). + getMaterialLimits(is.getWorld(), is.getUniqueId()). + getOrDefault(m, -1); + + if (limit > 0) { + limit -= this.getBlockLimitListener().getIsland(is).getBlockLimitOffset(m); + } + + return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); + } + + private String getLimit(@Nullable User user, EntityType e, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null) { + return LIMIT_NOT_SET; + } + + int limit = this.getBlockLimitListener().getIsland(is).getEntityLimit(e); + if (limit < 0 && this.getSettings().getLimits().containsKey(e)) { + limit = this.getSettings().getLimits().get(e); + } + + return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); + } + + private String getBaseLimit(@Nullable User user, EntityType e, GameModeAddon gm) { + Island is = gm.getIslands().getIsland(gm.getOverWorld(), user); + if (is == null || !this.getSettings().getLimits().containsKey(e)) { + return LIMIT_NOT_SET; + } + + int limit = this.getSettings().getLimits().get(e); + + return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit); + } + + +} diff --git a/src/main/java/world/bentobox/limits/LimitsPladdon.java b/src/main/java/world/bentobox/limits/LimitsPladdon.java index 53a5ea7..039b85d 100644 --- a/src/main/java/world/bentobox/limits/LimitsPladdon.java +++ b/src/main/java/world/bentobox/limits/LimitsPladdon.java @@ -1,19 +1,19 @@ -package world.bentobox.limits; - - -import world.bentobox.bentobox.api.addons.Addon; -import world.bentobox.bentobox.api.addons.Pladdon; - - -public class LimitsPladdon extends Pladdon { - - private Addon addon; - - @Override - public Addon getAddon() { - if (addon == null) { - addon = new Limits(); - } - return addon; - } -} +package world.bentobox.limits; + + +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.addons.Pladdon; + + +public class LimitsPladdon extends Pladdon { + + private Addon addon; + + @Override + public Addon getAddon() { + if (addon == null) { + addon = new Limits(); + } + return addon; + } +} diff --git a/src/main/java/world/bentobox/limits/Settings.java b/src/main/java/world/bentobox/limits/Settings.java index dea309d..b190faf 100644 --- a/src/main/java/world/bentobox/limits/Settings.java +++ b/src/main/java/world/bentobox/limits/Settings.java @@ -1,174 +1,174 @@ -package world.bentobox.limits; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.EntityType; - -public class Settings { - - enum GeneralGroup { - ANIMALS, MOBS - } - - private final Map general = new EnumMap<>(GeneralGroup.class); - private final Map limits = new EnumMap<>(EntityType.class); - private final Map> groupLimits = new EnumMap<>(EntityType.class); - private final List gameModes; - private final boolean asyncGolums; - private static final List DISALLOWED = Arrays.asList( - EntityType.TNT, - EntityType.EVOKER_FANGS, - EntityType.LLAMA_SPIT, - EntityType.DRAGON_FIREBALL, - EntityType.AREA_EFFECT_CLOUD, - EntityType.END_CRYSTAL, - EntityType.SMALL_FIREBALL, - EntityType.FIREBALL, - EntityType.EXPERIENCE_BOTTLE, - EntityType.EXPERIENCE_ORB, - EntityType.SHULKER_BULLET, - EntityType.WITHER_SKULL, - EntityType.TRIDENT, - EntityType.ARROW, - EntityType.SPECTRAL_ARROW, - EntityType.SNOWBALL, - EntityType.EGG, - EntityType.LEASH_KNOT, - EntityType.GIANT, - EntityType.ENDER_PEARL, - EntityType.ENDER_DRAGON, - EntityType.ITEM_FRAME, - EntityType.PAINTING); - - public Settings(Limits addon) { - - // GameModes - gameModes = addon.getConfig().getStringList("gamemodes"); - - ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits"); - if (el != null) { - for (String key : el.getKeys(false)) { - if (key.equalsIgnoreCase("ANIMALS")) { - general.put(GeneralGroup.ANIMALS, el.getInt(key, 0)); - } else if (key.equalsIgnoreCase("MOBS")) { - general.put(GeneralGroup.MOBS, el.getInt(key, 0)); - } else { - EntityType type = getType(key); - if (type != null) { - if (DISALLOWED.contains(type)) { - addon.logError("Entity type: " + key + " is not supported - skipping..."); - } else { - limits.put(type, el.getInt(key, 0)); - } - } else { - addon.logError("Unknown entity type: " + key + " - skipping..."); - } - } - } - } - // Async Golums - asyncGolums = addon.getConfig().getBoolean("async-golums", true); - - addon.log("Entity limits:"); - limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log); - - //group limits - el = addon.getConfig().getConfigurationSection("entitygrouplimits"); - if (el != null) { - for (String name : el.getKeys(false)) { - int limit = el.getInt(name + ".limit"); - String iconName = el.getString(name + ".icon", "BARRIER"); - Material icon = Material.BARRIER; - try { - icon = Material.valueOf(iconName.toUpperCase(Locale.ENGLISH)); - } catch (Exception e) { - addon.logError("Invalid group icon name: " + iconName + ". Use a Bukkit Material."); - icon = Material.BARRIER; - } - Set entities = el.getStringList(name + ".entities").stream().map(s -> { - EntityType type = getType(s); - if (type != null) { - if (DISALLOWED.contains(type)) { - addon.logError("Entity type: " + s + " is not supported - skipping..."); - } else { - return type; - } - } else { - addon.logError("Unknown entity type: " + s + " - skipping..."); - } - return null; - }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new)); - if (entities.isEmpty()) - continue; - EntityGroup group = new EntityGroup(name, entities, limit, icon); - entities.forEach(e -> { - List groups = groupLimits.getOrDefault(e, new ArrayList<>()); - groups.add(group); - groupLimits.put(e, groups); - }); - } - } - - addon.log("Entity group limits:"); - getGroupLimitDefinitions().stream().map(e -> "Limit " + e.getName() + " (" + e.getTypes().stream().map(Enum::name).collect(Collectors.joining(", ")) + ") to " + e.getLimit()).forEach(addon::log); - } - - private EntityType getType(String key) { - return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null); - } - - /** - * @return the entity limits - */ - public Map getLimits() { - return Collections.unmodifiableMap(limits); - } - - /** - * @return the group limits - */ - public Map> getGroupLimits() { - return groupLimits; - } - - /** - * @return the group limit definitions - */ - public List getGroupLimitDefinitions() { - return groupLimits.values().stream().flatMap(Collection::stream).distinct().toList(); - } - - /** - * @return the gameModes - */ - public List getGameModes() { - return gameModes; - } - - /** - * @return the asyncGolums - */ - public boolean isAsyncGolums() { - return asyncGolums; - } - - /** - * @return the general coverage map - */ - public Map getGeneral() { - return general; - } -} +package world.bentobox.limits; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; + +public class Settings { + + enum GeneralGroup { + ANIMALS, MOBS + } + + private final Map general = new EnumMap<>(GeneralGroup.class); + private final Map limits = new EnumMap<>(EntityType.class); + private final Map> groupLimits = new EnumMap<>(EntityType.class); + private final List gameModes; + private final boolean asyncGolums; + private static final List DISALLOWED = Arrays.asList( + EntityType.TNT, + EntityType.EVOKER_FANGS, + EntityType.LLAMA_SPIT, + EntityType.DRAGON_FIREBALL, + EntityType.AREA_EFFECT_CLOUD, + EntityType.END_CRYSTAL, + EntityType.SMALL_FIREBALL, + EntityType.FIREBALL, + EntityType.EXPERIENCE_BOTTLE, + EntityType.EXPERIENCE_ORB, + EntityType.SHULKER_BULLET, + EntityType.WITHER_SKULL, + EntityType.TRIDENT, + EntityType.ARROW, + EntityType.SPECTRAL_ARROW, + EntityType.SNOWBALL, + EntityType.EGG, + EntityType.LEASH_KNOT, + EntityType.GIANT, + EntityType.ENDER_PEARL, + EntityType.ENDER_DRAGON, + EntityType.ITEM_FRAME, + EntityType.PAINTING); + + public Settings(Limits addon) { + + // GameModes + gameModes = addon.getConfig().getStringList("gamemodes"); + + ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits"); + if (el != null) { + for (String key : el.getKeys(false)) { + if (key.equalsIgnoreCase("ANIMALS")) { + general.put(GeneralGroup.ANIMALS, el.getInt(key, 0)); + } else if (key.equalsIgnoreCase("MOBS")) { + general.put(GeneralGroup.MOBS, el.getInt(key, 0)); + } else { + EntityType type = getType(key); + if (type != null) { + if (DISALLOWED.contains(type)) { + addon.logError("Entity type: " + key + " is not supported - skipping..."); + } else { + limits.put(type, el.getInt(key, 0)); + } + } else { + addon.logError("Unknown entity type: " + key + " - skipping..."); + } + } + } + } + // Async Golums + asyncGolums = addon.getConfig().getBoolean("async-golums", true); + + addon.log("Entity limits:"); + limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log); + + //group limits + el = addon.getConfig().getConfigurationSection("entitygrouplimits"); + if (el != null) { + for (String name : el.getKeys(false)) { + int limit = el.getInt(name + ".limit"); + String iconName = el.getString(name + ".icon", "BARRIER"); + Material icon = Material.BARRIER; + try { + icon = Material.valueOf(iconName.toUpperCase(Locale.ENGLISH)); + } catch (Exception e) { + addon.logError("Invalid group icon name: " + iconName + ". Use a Bukkit Material."); + icon = Material.BARRIER; + } + Set entities = el.getStringList(name + ".entities").stream().map(s -> { + EntityType type = getType(s); + if (type != null) { + if (DISALLOWED.contains(type)) { + addon.logError("Entity type: " + s + " is not supported - skipping..."); + } else { + return type; + } + } else { + addon.logError("Unknown entity type: " + s + " - skipping..."); + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new)); + if (entities.isEmpty()) + continue; + EntityGroup group = new EntityGroup(name, entities, limit, icon); + entities.forEach(e -> { + List groups = groupLimits.getOrDefault(e, new ArrayList<>()); + groups.add(group); + groupLimits.put(e, groups); + }); + } + } + + addon.log("Entity group limits:"); + getGroupLimitDefinitions().stream().map(e -> "Limit " + e.getName() + " (" + e.getTypes().stream().map(Enum::name).collect(Collectors.joining(", ")) + ") to " + e.getLimit()).forEach(addon::log); + } + + private EntityType getType(String key) { + return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null); + } + + /** + * @return the entity limits + */ + public Map getLimits() { + return Collections.unmodifiableMap(limits); + } + + /** + * @return the group limits + */ + public Map> getGroupLimits() { + return groupLimits; + } + + /** + * @return the group limit definitions + */ + public List getGroupLimitDefinitions() { + return groupLimits.values().stream().flatMap(Collection::stream).distinct().toList(); + } + + /** + * @return the gameModes + */ + public List getGameModes() { + return gameModes; + } + + /** + * @return the asyncGolums + */ + public boolean isAsyncGolums() { + return asyncGolums; + } + + /** + * @return the general coverage map + */ + public Map getGeneral() { + return general; + } +} diff --git a/src/main/java/world/bentobox/limits/calculators/Pipeliner.java b/src/main/java/world/bentobox/limits/calculators/Pipeliner.java index 5d819cf..0a316af 100644 --- a/src/main/java/world/bentobox/limits/calculators/Pipeliner.java +++ b/src/main/java/world/bentobox/limits/calculators/Pipeliner.java @@ -1,151 +1,151 @@ -package world.bentobox.limits.calculators; - -import java.util.HashMap; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.Limits; -import world.bentobox.limits.calculators.Results.Result; - -/** - * A pipeliner that will process one island at a time - * @author tastybento - * - */ -public class Pipeliner { - - private static final int START_DURATION = 10; // 10 seconds - private static final int CONCURRENT_COUNTS = 1; - private final Queue toProcessQueue; - private final Map inProcessQueue; - private final BukkitTask task; - private final Limits addon; - private long time; - private long count; - - /** - * Construct the pipeliner - */ - public Pipeliner(Limits addon) { - this.addon = addon; - toProcessQueue = new ConcurrentLinkedQueue<>(); - inProcessQueue = new HashMap<>(); - // Loop continuously - check every tick if there is an island to scan - task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> { - if (!BentoBox.getInstance().isEnabled()) { - cancel(); - return; - } - // Complete the current to Process queue first - if (!inProcessQueue.isEmpty() || toProcessQueue.isEmpty()) return; - for (int j = 0; j < CONCURRENT_COUNTS && !toProcessQueue.isEmpty(); j++) { - RecountCalculator iD = toProcessQueue.poll(); - // Ignore deleted or unonwed islands - if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) { - inProcessQueue.put(iD, System.currentTimeMillis()); - // Start the scanning of a island with the first chunk - scanIsland(iD); - } - } - }, 1L, 10L); - } - - private void cancel() { - task.cancel(); - } - - /** - * @return number of islands currently in the queue or in process - */ - public int getIslandsInQueue() { - return inProcessQueue.size() + toProcessQueue.size(); - } - - /** - * Scans one chunk of an island and adds the results to a results object - * @param iD - */ - private void scanIsland(RecountCalculator iD) { - if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) { - // Island is deleted, so finish early with nothing - inProcessQueue.remove(iD); - iD.getR().complete(null); - return; - } - iD.scanIsland(this); - } - - - /** - * Adds an island to the scanning queue but only if the island is not already in the queue - * @param island - the island to scan - * @return CompletableFuture of the results. Results will be null if the island is already in the queue - */ - public CompletableFuture addIsland(Island island) { - // Check if queue already contains island - if (inProcessQueue.keySet().parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals) - || toProcessQueue.parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)) { - return CompletableFuture.completedFuture(new Results(Result.IN_PROGRESS)); - } - return addToQueue(island); - } - - private CompletableFuture addToQueue(Island island) { - CompletableFuture r = new CompletableFuture<>(); - toProcessQueue.add(new RecountCalculator(addon, island, r)); - count++; - return r; - } - - /** - * Get the average time it takes to run a level check - * @return the average time in seconds - */ - public int getTime() { - return time == 0 || count == 0 ? START_DURATION : (int)((double)time/count/1000); - } - - /** - * Submit how long a level check took - * @param time the time to set - */ - public void setTime(long time) { - // Running average - this.time += time; - } - - /** - * Stop the current queue. - */ - public void stop() { - addon.log("Stopping Level queue"); - task.cancel(); - this.inProcessQueue.clear(); - this.toProcessQueue.clear(); - } - - /** - * @return the inProcessQueue - */ - protected Map getInProcessQueue() { - return inProcessQueue; - } - - /** - * @return the task - */ - protected BukkitTask getTask() { - return task; - } - - - - -} +package world.bentobox.limits.calculators; + +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.Limits; +import world.bentobox.limits.calculators.Results.Result; + +/** + * A pipeliner that will process one island at a time + * @author tastybento + * + */ +public class Pipeliner { + + private static final int START_DURATION = 10; // 10 seconds + private static final int CONCURRENT_COUNTS = 1; + private final Queue toProcessQueue; + private final Map inProcessQueue; + private final BukkitTask task; + private final Limits addon; + private long time; + private long count; + + /** + * Construct the pipeliner + */ + public Pipeliner(Limits addon) { + this.addon = addon; + toProcessQueue = new ConcurrentLinkedQueue<>(); + inProcessQueue = new HashMap<>(); + // Loop continuously - check every tick if there is an island to scan + task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> { + if (!BentoBox.getInstance().isEnabled()) { + cancel(); + return; + } + // Complete the current to Process queue first + if (!inProcessQueue.isEmpty() || toProcessQueue.isEmpty()) return; + for (int j = 0; j < CONCURRENT_COUNTS && !toProcessQueue.isEmpty(); j++) { + RecountCalculator iD = toProcessQueue.poll(); + // Ignore deleted or unonwed islands + if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) { + inProcessQueue.put(iD, System.currentTimeMillis()); + // Start the scanning of a island with the first chunk + scanIsland(iD); + } + } + }, 1L, 10L); + } + + private void cancel() { + task.cancel(); + } + + /** + * @return number of islands currently in the queue or in process + */ + public int getIslandsInQueue() { + return inProcessQueue.size() + toProcessQueue.size(); + } + + /** + * Scans one chunk of an island and adds the results to a results object + * @param iD + */ + private void scanIsland(RecountCalculator iD) { + if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) { + // Island is deleted, so finish early with nothing + inProcessQueue.remove(iD); + iD.getR().complete(null); + return; + } + iD.scanIsland(this); + } + + + /** + * Adds an island to the scanning queue but only if the island is not already in the queue + * @param island - the island to scan + * @return CompletableFuture of the results. Results will be null if the island is already in the queue + */ + public CompletableFuture addIsland(Island island) { + // Check if queue already contains island + if (inProcessQueue.keySet().parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals) + || toProcessQueue.parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)) { + return CompletableFuture.completedFuture(new Results(Result.IN_PROGRESS)); + } + return addToQueue(island); + } + + private CompletableFuture addToQueue(Island island) { + CompletableFuture r = new CompletableFuture<>(); + toProcessQueue.add(new RecountCalculator(addon, island, r)); + count++; + return r; + } + + /** + * Get the average time it takes to run a level check + * @return the average time in seconds + */ + public int getTime() { + return time == 0 || count == 0 ? START_DURATION : (int)((double)time/count/1000); + } + + /** + * Submit how long a level check took + * @param time the time to set + */ + public void setTime(long time) { + // Running average + this.time += time; + } + + /** + * Stop the current queue. + */ + public void stop() { + addon.log("Stopping Level queue"); + task.cancel(); + this.inProcessQueue.clear(); + this.toProcessQueue.clear(); + } + + /** + * @return the inProcessQueue + */ + protected Map getInProcessQueue() { + return inProcessQueue; + } + + /** + * @return the task + */ + protected BukkitTask getTask() { + return task; + } + + + + +} diff --git a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java index 92f6293..82229dc 100644 --- a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java +++ b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java @@ -1,359 +1,359 @@ -package world.bentobox.limits.calculators; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.ChunkSnapshot; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.World.Environment; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Slab; -import org.bukkit.scheduler.BukkitTask; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Pair; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.Limits; -import world.bentobox.limits.calculators.Results.Result; -import world.bentobox.limits.listeners.BlockLimitsListener; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * Counter for limits - * @author tastybento - * - */ -public class RecountCalculator { - public static final long MAX_AMOUNT = 10000; - private static final int CHUNKS_TO_SCAN = 100; - private static final int CALCULATION_TIMEOUT = 5; // Minutes - - - private final Limits addon; - private final Queue> chunksToCheck; - private final Island island; - private final CompletableFuture r; - - - private final Results results; - private final Map worlds = new EnumMap<>(Environment.class); - private final List stackedBlocks = new ArrayList<>(); - private BukkitTask finishTask; - private final BlockLimitsListener bll; - private final World world; - private IslandBlockCount ibc; - - - /** - * Constructor to get the level for an island - * @param addon - addon - * @param island - the island to scan - * @param r - completable result that will be completed when the calculation is complete - */ - public RecountCalculator(Limits addon, Island island, CompletableFuture r) { - this.addon = addon; - this.bll = addon.getBlockLimitListener(); - this.island = island; - this.ibc = bll.getIsland(Objects.requireNonNull(island).getUniqueId()); - this.r = r; - results = new Results(); - chunksToCheck = getChunksToScan(island); - // Set up the worlds - this.world = Objects.requireNonNull(Util.getWorld(island.getWorld())); - worlds.put(Environment.NORMAL, world); - boolean isNether = addon.getPlugin().getIWM().isNetherGenerate(world) && addon.getPlugin().getIWM().isNetherIslands(world); - boolean isEnd = addon.getPlugin().getIWM().isEndGenerate(world) && addon.getPlugin().getIWM().isEndIslands(world); - - // Nether - if (isNether) { - World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); - if (nether != null) { - worlds.put(Environment.NETHER, nether); - } - } - // End - if (isEnd) { - World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); - if (end != null) { - worlds.put(Environment.THE_END, end); - } - } - } - - private void checkBlock(BlockData b) { - Material md = bll.fixMaterial(b); - // md is limited - if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) { - results.mdCount.add(md); - } - } - /** - * Get a set of all the chunks in island - * @param island - island - * @return - set of pairs of x,z coordinates to check - */ - private Queue> getChunksToScan(Island island) { - Queue> chunkQueue = new ConcurrentLinkedQueue<>(); - for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) { - for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { - chunkQueue.add(new Pair<>(x >> 4, z >> 4)); - } - } - return chunkQueue; - } - - - /** - * @return the island - */ - public Island getIsland() { - return island; - } - - /** - * Get the completable result for this calculation - * @return the r - */ - public CompletableFuture getR() { - return r; - } - - /** - * @return the results - */ - public Results getResults() { - return results; - } - - /** - * Get a chunk async - * @param env - the environment - * @param x - chunk x coordinate - * @param z - chunk z coordinate - * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether - */ - private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { - if (worlds.containsKey(env)) { - CompletableFuture> r2 = new CompletableFuture<>(); - List chunkList = new ArrayList<>(); - World world = worlds.get(env); - // Get the chunk, and then coincidentally check the RoseStacker - loadChunks(r2, world, pairList, chunkList); - return r2; - } - return CompletableFuture.completedFuture(Collections.emptyList()); - } - - private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, - List chunkList) { - if (pairList.isEmpty()) { - r2.complete(chunkList); - return; - } - Pair p = pairList.poll(); - Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { - if (chunk != null) { - chunkList.add(chunk); - // roseStackerCheck(chunk); - } - loadChunks(r2, world, pairList, chunkList); // Iteration - }); - } - /* - private void roseStackerCheck(Chunk chunk) { - if (addon.isRoseStackersEnabled()) { - RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { - // Blocks below sea level can be scored differently - boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; - // Check block once because the base block will be counted in the chunk snapshot - for (int _x = 0; _x < e.getStackSize() - 1; _x++) { - checkBlock(e.getBlock().getType(), belowSeaLevel); - } - }); - } - } - */ - /** - * Count the blocks on the island - * @param chunk chunk to scan - */ - private void scanAsync(Chunk chunk) { - ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot(); - for (int x = 0; x< 16; x++) { - // Check if the block coordinate is inside the protection zone and if not, don't count it - if (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { - continue; - } - for (int z = 0; z < 16; z++) { - // Check if the block coordinate is inside the protection zone and if not, don't count it - if (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { - continue; - } - // Only count to the highest block in the world for some optimization - for (int y = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) { - BlockData blockData = chunkSnapshot.getBlockData(x, y, z); - // Slabs can be doubled, so check them twice - if (Tag.SLABS.isTagged(blockData.getMaterial())) { - Slab slab = (Slab)blockData; - if (slab.getType().equals(Slab.Type.DOUBLE)) { - checkBlock(blockData); - } - } - // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk - /* - if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) { - stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16)); - } - */ - // Add the value of the block's material - checkBlock(blockData); - } - } - } - } - - /** - * Scan the chunk chests and count the blocks - * @param chunks - the chunk to scan - * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not - */ - private CompletableFuture scanChunk(List chunks) { - // If the chunk hasn't been generated, return - if (chunks == null || chunks.isEmpty()) { - return CompletableFuture.completedFuture(false); - } - // Count blocks in chunk - CompletableFuture result = new CompletableFuture<>(); - - Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { - chunks.forEach(chunk -> scanAsync(chunk)); - Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true)); - }); - return result; - } - - /** - * Scan the next chunk on the island - * @return completable boolean future that will be true if more chunks are left to be scanned, and false if not - */ - public CompletableFuture scanNextChunk() { - if (chunksToCheck.isEmpty()) { - addon.logError("Unexpected: no chunks to scan!"); - // This should not be needed, but just in case - return CompletableFuture.completedFuture(false); - } - // Retrieve and remove from the queue - Queue> pairList = new ConcurrentLinkedQueue<>(); - int i = 0; - while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) { - pairList.add(chunksToCheck.poll()); - } - Queue> endPairList = new ConcurrentLinkedQueue<>(pairList); - Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList); - // Set up the result - CompletableFuture result = new CompletableFuture<>(); - // Get chunks and scan - // Get chunks and scan - getWorldChunk(Environment.THE_END, endPairList).thenAccept(endChunks -> - scanChunk(endChunks).thenAccept(b -> - getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks -> - scanChunk(netherChunks).thenAccept(b2 -> - getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks -> - scanChunk(normalChunks).thenAccept(b3 -> - // Complete the result now that all chunks have been scanned - result.complete(!chunksToCheck.isEmpty())))) - ) - ) - ); - - return result; - } - - /** - * Finalizes the calculations and makes the report - */ - public void tidyUp() { - // Finalize calculations - if (ibc == null) { - ibc = new IslandBlockCount(island.getUniqueId(), addon.getPlugin().getIWM().getAddon(world).map(a -> a.getDescription().getName()).orElse("default")); - } - ibc.getBlockCounts().clear(); - results.getMdCount().forEach(ibc::add); - bll.setIsland(island.getUniqueId(), ibc); - //Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished")); - - // All done. - } - - public void scanIsland(Pipeliner pipeliner) { - // Scan the next chunk - scanNextChunk().thenAccept(r -> { - if (!Bukkit.isPrimaryThread()) { - addon.getPlugin().logError("scanChunk not on Primary Thread!"); - } - // Timeout check - if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > CALCULATION_TIMEOUT * 60000) { - // Done - pipeliner.getInProcessQueue().remove(this); - getR().complete(new Results(Result.TIMEOUT)); - addon.logError("Level calculation timed out after " + CALCULATION_TIMEOUT + "m for island: " + getIsland()); - return; - } - if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) { - // scanNextChunk returns true if there are more chunks to scan - scanIsland(pipeliner); - } else { - // Done - pipeliner.getInProcessQueue().remove(this); - // Chunk finished - // This was the last chunk - handleStackedBlocks(); - long checkTime = System.currentTimeMillis(); - finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { - // Check every half second if all the chests and stacks have been cleared - if ((stackedBlocks.isEmpty()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) { - this.tidyUp(); - this.getR().complete(getResults()); - finishTask.cancel(); - } - }, 0, 10L); - - } - }); - } - - private void handleStackedBlocks() { - // Deal with any stacked blocks - /* - Iterator it = stackedBlocks.iterator(); - while (it.hasNext()) { - Location v = it.next(); - Util.getChunkAtAsync(v).thenAccept(c -> { - Block cauldronBlock = v.getBlock(); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } - it.remove(); - }); - } - */ - } -} +package world.bentobox.limits.calculators; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; +import org.bukkit.scheduler.BukkitTask; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.Limits; +import world.bentobox.limits.calculators.Results.Result; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * Counter for limits + * @author tastybento + * + */ +public class RecountCalculator { + public static final long MAX_AMOUNT = 10000; + private static final int CHUNKS_TO_SCAN = 100; + private static final int CALCULATION_TIMEOUT = 5; // Minutes + + + private final Limits addon; + private final Queue> chunksToCheck; + private final Island island; + private final CompletableFuture r; + + + private final Results results; + private final Map worlds = new EnumMap<>(Environment.class); + private final List stackedBlocks = new ArrayList<>(); + private BukkitTask finishTask; + private final BlockLimitsListener bll; + private final World world; + private IslandBlockCount ibc; + + + /** + * Constructor to get the level for an island + * @param addon - addon + * @param island - the island to scan + * @param r - completable result that will be completed when the calculation is complete + */ + public RecountCalculator(Limits addon, Island island, CompletableFuture r) { + this.addon = addon; + this.bll = addon.getBlockLimitListener(); + this.island = island; + this.ibc = bll.getIsland(Objects.requireNonNull(island).getUniqueId()); + this.r = r; + results = new Results(); + chunksToCheck = getChunksToScan(island); + // Set up the worlds + this.world = Objects.requireNonNull(Util.getWorld(island.getWorld())); + worlds.put(Environment.NORMAL, world); + boolean isNether = addon.getPlugin().getIWM().isNetherGenerate(world) && addon.getPlugin().getIWM().isNetherIslands(world); + boolean isEnd = addon.getPlugin().getIWM().isEndGenerate(world) && addon.getPlugin().getIWM().isEndIslands(world); + + // Nether + if (isNether) { + World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); + if (nether != null) { + worlds.put(Environment.NETHER, nether); + } + } + // End + if (isEnd) { + World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); + if (end != null) { + worlds.put(Environment.THE_END, end); + } + } + } + + private void checkBlock(BlockData b) { + Material md = bll.fixMaterial(b); + // md is limited + if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) { + results.mdCount.add(md); + } + } + /** + * Get a set of all the chunks in island + * @param island - island + * @return - set of pairs of x,z coordinates to check + */ + private Queue> getChunksToScan(Island island) { + Queue> chunkQueue = new ConcurrentLinkedQueue<>(); + for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) { + for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { + chunkQueue.add(new Pair<>(x >> 4, z >> 4)); + } + } + return chunkQueue; + } + + + /** + * @return the island + */ + public Island getIsland() { + return island; + } + + /** + * Get the completable result for this calculation + * @return the r + */ + public CompletableFuture getR() { + return r; + } + + /** + * @return the results + */ + public Results getResults() { + return results; + } + + /** + * Get a chunk async + * @param env - the environment + * @param x - chunk x coordinate + * @param z - chunk z coordinate + * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether + */ + private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { + if (worlds.containsKey(env)) { + CompletableFuture> r2 = new CompletableFuture<>(); + List chunkList = new ArrayList<>(); + World world = worlds.get(env); + // Get the chunk, and then coincidentally check the RoseStacker + loadChunks(r2, world, pairList, chunkList); + return r2; + } + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, + List chunkList) { + if (pairList.isEmpty()) { + r2.complete(chunkList); + return; + } + Pair p = pairList.poll(); + Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { + if (chunk != null) { + chunkList.add(chunk); + // roseStackerCheck(chunk); + } + loadChunks(r2, world, pairList, chunkList); // Iteration + }); + } + /* + private void roseStackerCheck(Chunk chunk) { + if (addon.isRoseStackersEnabled()) { + RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { + // Blocks below sea level can be scored differently + boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; + // Check block once because the base block will be counted in the chunk snapshot + for (int _x = 0; _x < e.getStackSize() - 1; _x++) { + checkBlock(e.getBlock().getType(), belowSeaLevel); + } + }); + } + } + */ + /** + * Count the blocks on the island + * @param chunk chunk to scan + */ + private void scanAsync(Chunk chunk) { + ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot(); + for (int x = 0; x< 16; x++) { + // Check if the block coordinate is inside the protection zone and if not, don't count it + if (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { + continue; + } + for (int z = 0; z < 16; z++) { + // Check if the block coordinate is inside the protection zone and if not, don't count it + if (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { + continue; + } + // Only count to the highest block in the world for some optimization + for (int y = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) { + BlockData blockData = chunkSnapshot.getBlockData(x, y, z); + // Slabs can be doubled, so check them twice + if (Tag.SLABS.isTagged(blockData.getMaterial())) { + Slab slab = (Slab)blockData; + if (slab.getType().equals(Slab.Type.DOUBLE)) { + checkBlock(blockData); + } + } + // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk + /* + if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) { + stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16)); + } + */ + // Add the value of the block's material + checkBlock(blockData); + } + } + } + } + + /** + * Scan the chunk chests and count the blocks + * @param chunks - the chunk to scan + * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not + */ + private CompletableFuture scanChunk(List chunks) { + // If the chunk hasn't been generated, return + if (chunks == null || chunks.isEmpty()) { + return CompletableFuture.completedFuture(false); + } + // Count blocks in chunk + CompletableFuture result = new CompletableFuture<>(); + + Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { + chunks.forEach(chunk -> scanAsync(chunk)); + Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true)); + }); + return result; + } + + /** + * Scan the next chunk on the island + * @return completable boolean future that will be true if more chunks are left to be scanned, and false if not + */ + public CompletableFuture scanNextChunk() { + if (chunksToCheck.isEmpty()) { + addon.logError("Unexpected: no chunks to scan!"); + // This should not be needed, but just in case + return CompletableFuture.completedFuture(false); + } + // Retrieve and remove from the queue + Queue> pairList = new ConcurrentLinkedQueue<>(); + int i = 0; + while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) { + pairList.add(chunksToCheck.poll()); + } + Queue> endPairList = new ConcurrentLinkedQueue<>(pairList); + Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList); + // Set up the result + CompletableFuture result = new CompletableFuture<>(); + // Get chunks and scan + // Get chunks and scan + getWorldChunk(Environment.THE_END, endPairList).thenAccept(endChunks -> + scanChunk(endChunks).thenAccept(b -> + getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks -> + scanChunk(netherChunks).thenAccept(b2 -> + getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks -> + scanChunk(normalChunks).thenAccept(b3 -> + // Complete the result now that all chunks have been scanned + result.complete(!chunksToCheck.isEmpty())))) + ) + ) + ); + + return result; + } + + /** + * Finalizes the calculations and makes the report + */ + public void tidyUp() { + // Finalize calculations + if (ibc == null) { + ibc = new IslandBlockCount(island.getUniqueId(), addon.getPlugin().getIWM().getAddon(world).map(a -> a.getDescription().getName()).orElse("default")); + } + ibc.getBlockCounts().clear(); + results.getMdCount().forEach(ibc::add); + bll.setIsland(island.getUniqueId(), ibc); + //Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished")); + + // All done. + } + + public void scanIsland(Pipeliner pipeliner) { + // Scan the next chunk + scanNextChunk().thenAccept(r -> { + if (!Bukkit.isPrimaryThread()) { + addon.getPlugin().logError("scanChunk not on Primary Thread!"); + } + // Timeout check + if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > CALCULATION_TIMEOUT * 60000) { + // Done + pipeliner.getInProcessQueue().remove(this); + getR().complete(new Results(Result.TIMEOUT)); + addon.logError("Level calculation timed out after " + CALCULATION_TIMEOUT + "m for island: " + getIsland()); + return; + } + if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) { + // scanNextChunk returns true if there are more chunks to scan + scanIsland(pipeliner); + } else { + // Done + pipeliner.getInProcessQueue().remove(this); + // Chunk finished + // This was the last chunk + handleStackedBlocks(); + long checkTime = System.currentTimeMillis(); + finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { + // Check every half second if all the chests and stacks have been cleared + if ((stackedBlocks.isEmpty()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) { + this.tidyUp(); + this.getR().complete(getResults()); + finishTask.cancel(); + } + }, 0, 10L); + + } + }); + } + + private void handleStackedBlocks() { + // Deal with any stacked blocks + /* + Iterator it = stackedBlocks.iterator(); + while (it.hasNext()) { + Location v = it.next(); + Util.getChunkAtAsync(v).thenAccept(c -> { + Block cauldronBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } + it.remove(); + }); + } + */ + } +} diff --git a/src/main/java/world/bentobox/limits/calculators/Results.java b/src/main/java/world/bentobox/limits/calculators/Results.java index b2c01e0..9d07619 100644 --- a/src/main/java/world/bentobox/limits/calculators/Results.java +++ b/src/main/java/world/bentobox/limits/calculators/Results.java @@ -1,57 +1,57 @@ -package world.bentobox.limits.calculators; - -import org.bukkit.Material; -import org.bukkit.entity.EntityType; - -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; - -public class Results { - public enum Result { - /** - * A level calc is already in progress - */ - IN_PROGRESS, - /** - * Results will be available - */ - AVAILABLE, - /** - * Result if calculation timed out - */ - TIMEOUT - } - final Multiset mdCount = HashMultiset.create(); - final Multiset entityCount = HashMultiset.create(); - - final Result state; - - public Results(Result state) { - this.state = state; - } - - public Results() { - this.state = Result.AVAILABLE; - } - /** - * @return the mdCount - */ - public Multiset getMdCount() { - return mdCount; - } - - /** - * @return the state - */ - public Result getState() { - return state; - } - - /** - * @return the entityCount - */ - public Multiset getEntityCount() { - return entityCount; - } - +package world.bentobox.limits.calculators; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; + +public class Results { + public enum Result { + /** + * A level calc is already in progress + */ + IN_PROGRESS, + /** + * Results will be available + */ + AVAILABLE, + /** + * Result if calculation timed out + */ + TIMEOUT + } + final Multiset mdCount = HashMultiset.create(); + final Multiset entityCount = HashMultiset.create(); + + final Result state; + + public Results(Result state) { + this.state = state; + } + + public Results() { + this.state = Result.AVAILABLE; + } + /** + * @return the mdCount + */ + public Multiset getMdCount() { + return mdCount; + } + + /** + * @return the state + */ + public Result getState() { + return state; + } + + /** + * @return the entityCount + */ + public Multiset getEntityCount() { + return entityCount; + } + } \ No newline at end of file diff --git a/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java b/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java index 263b24e..ac99e10 100644 --- a/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java +++ b/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java @@ -1,81 +1,81 @@ -package world.bentobox.limits.commands.admin; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.Limits; -import world.bentobox.limits.commands.player.LimitPanel; - - -/** - * Admin command for limits - * @author tastybento - * - */ -public class AdminCommand extends CompositeCommand { - - private final Limits addon; - - /** - * Admin command - * @param addon - addon - */ - public AdminCommand(Limits addon, CompositeCommand parent) { - super(parent, "limits"); - this.addon = addon; - - new CalcCommand(this.addon, this); - new OffsetCommand(this.addon, this); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() - */ - @Override - public void setup() { - this.setPermission("limits.admin.limits"); - this.setOnlyPlayer(true); - this.setParametersHelp("admin.limits.main.parameters"); - this.setDescription("admin.limits.main.description"); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) - */ - @Override - public boolean execute(User user, String label, List args) { - if (args.size() == 1) { - // Asking for another player's limits - // Convert name to a UUID - final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); - if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player", args.get(0)); - return true; - } else { - new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, playerUUID); - } - return true; - } else { - showHelp(this, user); - return false; - } - } - - @Override - public Optional> tabComplete(User user, String alias, List args) { - String lastArg = !args.isEmpty() ? args.get(args.size()-1) : ""; - if (args.isEmpty()) { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); - } - List options = new ArrayList<>(Util.getOnlinePlayerList(user)); - return Optional.of(Util.tabLimit(options, lastArg)); - } - -} +package world.bentobox.limits.commands.admin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.Limits; +import world.bentobox.limits.commands.player.LimitPanel; + + +/** + * Admin command for limits + * @author tastybento + * + */ +public class AdminCommand extends CompositeCommand { + + private final Limits addon; + + /** + * Admin command + * @param addon - addon + */ + public AdminCommand(Limits addon, CompositeCommand parent) { + super(parent, "limits"); + this.addon = addon; + + new CalcCommand(this.addon, this); + new OffsetCommand(this.addon, this); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() + */ + @Override + public void setup() { + this.setPermission("limits.admin.limits"); + this.setOnlyPlayer(true); + this.setParametersHelp("admin.limits.main.parameters"); + this.setDescription("admin.limits.main.description"); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + */ + @Override + public boolean execute(User user, String label, List args) { + if (args.size() == 1) { + // Asking for another player's limits + // Convert name to a UUID + final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); + if (playerUUID == null) { + user.sendMessage("general.errors.unknown-player", args.get(0)); + return true; + } else { + new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, playerUUID); + } + return true; + } else { + showHelp(this, user); + return false; + } + } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size()-1) : ""; + if (args.isEmpty()) { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + List options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } + +} diff --git a/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java b/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java index f7e7303..08f7f7d 100644 --- a/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java +++ b/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java @@ -1,95 +1,95 @@ -package world.bentobox.limits.commands.admin; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.Limits; -import world.bentobox.limits.calculators.Pipeliner; - -/** - * - * @author YellowZaki, tastybento - */ -public class CalcCommand extends CompositeCommand { - - private final Limits addon; - private Island island; - - /** - * Admin command - * - * @param addon - addon - */ - public CalcCommand(Limits addon, CompositeCommand parent) { - super(parent, "calc", "recount"); - this.addon = addon; - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() - */ - @Override - public void setup() { - this.setPermission("limits.admin.limits.calc"); - this.setOnlyPlayer(false); - this.setParametersHelp("admin.limits.calc.parameters"); - this.setDescription("admin.limits.calc.description"); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) - */ - @Override - public boolean execute(User user, String label, List args) { - if (args.size() == 1) { - final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); - if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player", args.get(0)); - return true; - } - island = addon.getIslands().getIsland(getWorld(), playerUUID); - if (island == null) { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } else { - //Calculate - user.sendMessage("island.limits.recount.now-recounting"); - new Pipeliner(addon).addIsland(island).thenAccept(results -> { - if (results == null) { - user.sendMessage("island.limits.recount.in-progress"); - } else { - switch (results.getState()) { - case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout"); - default -> user.sendMessage("admin.limits.calc.finished"); - } - } - }); - } - - return true; - } else { - showHelp(this, user); - return false; - } - } - - - - @Override - public Optional> tabComplete(User user, String alias, List args) { - String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; - if (args.isEmpty()) { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); - } - List options = new ArrayList<>(Util.getOnlinePlayerList(user)); - return Optional.of(Util.tabLimit(options, lastArg)); - } - -} +package world.bentobox.limits.commands.admin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.Limits; +import world.bentobox.limits.calculators.Pipeliner; + +/** + * + * @author YellowZaki, tastybento + */ +public class CalcCommand extends CompositeCommand { + + private final Limits addon; + private Island island; + + /** + * Admin command + * + * @param addon - addon + */ + public CalcCommand(Limits addon, CompositeCommand parent) { + super(parent, "calc", "recount"); + this.addon = addon; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() + */ + @Override + public void setup() { + this.setPermission("limits.admin.limits.calc"); + this.setOnlyPlayer(false); + this.setParametersHelp("admin.limits.calc.parameters"); + this.setDescription("admin.limits.calc.description"); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + */ + @Override + public boolean execute(User user, String label, List args) { + if (args.size() == 1) { + final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); + if (playerUUID == null) { + user.sendMessage("general.errors.unknown-player", args.get(0)); + return true; + } + island = addon.getIslands().getIsland(getWorld(), playerUUID); + if (island == null) { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } else { + //Calculate + user.sendMessage("island.limits.recount.now-recounting"); + new Pipeliner(addon).addIsland(island).thenAccept(results -> { + if (results == null) { + user.sendMessage("island.limits.recount.in-progress"); + } else { + switch (results.getState()) { + case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout"); + default -> user.sendMessage("admin.limits.calc.finished"); + } + } + }); + } + + return true; + } else { + showHelp(this, user); + return false; + } + } + + + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + if (args.isEmpty()) { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + List options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } + +} diff --git a/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java b/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java index 3ab71b9..a675596 100644 --- a/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java +++ b/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java @@ -1,695 +1,695 @@ -// -// Created by BONNe -// Copyright - 2022 -// - - -package world.bentobox.limits.commands.admin; - - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.entity.EntityType; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.base.Enums; - -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.Limits; -import world.bentobox.limits.objects.IslandBlockCount; - - -/** - * This command manages offsets to the player island limits. - */ -public class OffsetCommand extends CompositeCommand -{ - /** - * Instantiates a new Offset command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetCommand(Limits addon, CompositeCommand parent) - { - super(parent, "offset"); - - new OffsetSetCommand(addon, this); - new OffsetAddCommand(addon, this); - new OffsetRemoveCommand(addon, this); - new OffsetResetCommand(addon, this); - new OffsetDisplayCommand(addon, this); - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset"); - this.setParametersHelp("admin.limits.offset.parameters"); - this.setDescription("admin.limits.offset.description"); - } - - - @Override - public boolean execute(User user, String s, List list) - { - this.showHelp(this, user); - return true; - } - - - /** - * This command allows setting limit offset for material or entity. - */ - private static class OffsetSetCommand extends CompositeCommand - { - /** - * Instantiates a new Offset set command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetSetCommand(Limits addon, CompositeCommand parent) - { - super(parent, "set"); - this.addon = addon; - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset.set"); - this.setParametersHelp("admin.limits.offset.set.parameters"); - this.setDescription("admin.limits.offset.set.description"); - } - - - @Override - public boolean execute(User user, String label, List args) - { - if (args.size() != 3) - { - // Show help - this.showHelp(this, user); - return false; - } - - // Get target player - UUID targetUUID = Util.getUUID(args.get(0)); - - if (targetUUID == null) - { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } - - Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); - - if (island == null) - { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } - - IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); - - // Get new offset - if (!Util.isInteger(args.get(2), true)) - { - user.sendMessage("general.errors.must-be-a-number", TextVariables.NUMBER, args.get(2)); - return false; - } - - Material material = Material.matchMaterial(args.get(1)); - EntityType entityType = matchEntity(args.get(1)); - - if (material == null && entityType == null) - { - user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); - return false; - } - - int offset = Integer.parseInt(args.get(2)); - - if (material != null && offset == islandData.getBlockLimitOffset(material) || - entityType != null && offset == islandData.getEntityLimitOffset(entityType)) - { - user.sendMessage("admin.limits.offset.set.same", - TextVariables.NAME, - args.get(1), - TextVariables.NUMBER, - args.get(2)); - return false; - } - - if (material != null) - { - islandData.setBlockLimitsOffset(material, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.set.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, material.name()); - } - else - { - islandData.setEntityLimitsOffset(entityType, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.set.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, entityType.name()); - } - - return true; - } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return OffsetCommand.craftTabComplete(user, alias, args); - } - - - /** - * Instance of limits addon. - */ - private final Limits addon; - } - - - /** - * This command allows increasing limit offset for material or entity. - */ - private static class OffsetAddCommand extends CompositeCommand - { - /** - * Instantiates a new Offset add command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetAddCommand(Limits addon, CompositeCommand parent) - { - super(parent, "add"); - this.addon = addon; - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset.add"); - this.setParametersHelp("admin.limits.offset.add.parameters"); - this.setDescription("admin.limits.offset.add.description"); - } - - - @Override - public boolean execute(User user, String label, List args) - { - if (args.size() != 3) - { - // Show help - this.showHelp(this, user); - return false; - } - - // Get target player - UUID targetUUID = Util.getUUID(args.get(0)); - - if (targetUUID == null) - { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } - - Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); - - if (island == null) - { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } - - IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); - - // Get new offset - if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0) - { - user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2)); - return false; - } - - Material material = Material.matchMaterial(args.get(1)); - EntityType entityType = matchEntity(args.get(1)); - - if (material == null && entityType == null) - { - user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); - return false; - } - - int offset = Integer.parseInt(args.get(2)); - - if (material != null) - { - offset += islandData.getBlockLimitOffset(material); - - islandData.setBlockLimitsOffset(material, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.add.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, material.name()); - } - else - { - offset += islandData.getEntityLimitOffset(entityType); - - islandData.setEntityLimitsOffset(entityType, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.add.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, entityType.name()); - } - - return true; - } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return OffsetCommand.craftTabComplete(user, alias, args); - } - - - /** - * Instance of limits addon. - */ - private final Limits addon; - } - - - /** - * This command allows reducing limit offset for material or entity. - */ - private static class OffsetRemoveCommand extends CompositeCommand - { - /** - * Instantiates a new Offset remove command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetRemoveCommand(Limits addon, CompositeCommand parent) - { - super(parent, "remove"); - this.addon = addon; - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset.remove"); - this.setParametersHelp("admin.limits.offset.remove.parameters"); - this.setDescription("admin.limits.offset.remove.description"); - } - - - @Override - public boolean execute(User user, String label, List args) - { - if (args.size() != 3) - { - // Show help - this.showHelp(this, user); - return false; - } - - // Get target player - UUID targetUUID = Util.getUUID(args.get(0)); - - if (targetUUID == null) - { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } - - Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); - - if (island == null) - { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } - - IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); - - // Get new offset - if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0) - { - user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2)); - return false; - } - - Material material = Material.matchMaterial(args.get(1)); - EntityType entityType = matchEntity(args.get(1)); - - if (material == null && entityType == null) - { - user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); - return false; - } - - int offset = Integer.parseInt(args.get(2)); - - if (material != null) - { - offset = islandData.getBlockLimitOffset(material) - offset; - - islandData.setBlockLimitsOffset(material, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.remove.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, material.name()); - } - else - { - offset = islandData.getEntityLimitOffset(entityType) - offset; - - islandData.setEntityLimitsOffset(entityType, offset); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.remove.success", - TextVariables.NUMBER, String.valueOf(offset), - TextVariables.NAME, entityType.name()); - } - - return true; - } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return OffsetCommand.craftTabComplete(user, alias, args); - } - - - /** - * Instance of limits addon. - */ - private final Limits addon; - } - - - /** - * This command allows resetting limit offset for material or entity. - */ - private static class OffsetResetCommand extends CompositeCommand - { - /** - * Instantiates a new Offset reset command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetResetCommand(Limits addon, CompositeCommand parent) - { - super(parent, "reset"); - this.addon = addon; - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset.reset"); - this.setParametersHelp("admin.limits.offset.reset.parameters"); - this.setDescription("admin.limits.offset.reset.description"); - } - - - @Override - public boolean execute(User user, String label, List args) - { - if (args.size() != 2) - { - // Show help - this.showHelp(this, user); - return false; - } - - // Get target player - UUID targetUUID = Util.getUUID(args.get(0)); - - if (targetUUID == null) - { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } - - Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); - - if (island == null) - { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } - - IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); - - Material material = Material.matchMaterial(args.get(1)); - EntityType entityType = matchEntity(args.get(1)); - - if (material == null && entityType == null) - { - user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); - return false; - } - - if (material != null) - { - islandData.setBlockLimitsOffset(material, 0); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.reset.success", - TextVariables.NAME, material.name()); - } - else - { - islandData.setEntityLimitsOffset(entityType, 0); - islandData.setChanged(); - - user.sendMessage("admin.limits.offset.reset.success", - TextVariables.NAME, entityType.name()); - } - - return true; - } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return OffsetCommand.craftTabComplete(user, alias, args); - } - - - /** - * Instance of limits addon. - */ - private final Limits addon; - } - - - /** - * This command allows viewing limit offset for material or entity. - */ - private static class OffsetDisplayCommand extends CompositeCommand - { - /** - * Instantiates a new Offset display command. - * - * @param addon the addon - * @param parent the parent - */ - public OffsetDisplayCommand(Limits addon, CompositeCommand parent) - { - super(parent, "view", "display"); - this.addon = addon; - } - - - @Override - public void setup() - { - this.setOnlyPlayer(false); - - this.setPermission("admin.limits.offset.view"); - this.setParametersHelp("admin.limits.offset.view.parameters"); - this.setDescription("admin.limits.offset.view.description"); - } - - - @Override - public boolean execute(User user, String label, List args) - { - if (args.size() != 2) - { - // Show help - this.showHelp(this, user); - return false; - } - - // Get target player - UUID targetUUID = Util.getUUID(args.get(0)); - - if (targetUUID == null) - { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } - - Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); - - if (island == null) - { - user.sendMessage("general.errors.player-has-no-island"); - return false; - } - - IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); - - Material material = Material.matchMaterial(args.get(1)); - EntityType entityType = matchEntity(args.get(1)); - - if (material == null && entityType == null) - { - user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); - return false; - } - - if (material != null) - { - int offset = islandData.getBlockLimitOffset(material); - user.sendMessage("admin.limits.offset.view.message", - TextVariables.NAME, material.name(), - TextVariables.NUMBER, String.valueOf(offset)); - } - else - { - int offset = islandData.getEntityLimitOffset(entityType); - user.sendMessage("admin.limits.offset.view.message", - TextVariables.NAME, entityType.name(), - TextVariables.NUMBER, String.valueOf(offset)); - } - - return true; - } - - - @Override - public Optional> tabComplete(User user, String alias, List args) - { - return OffsetCommand.craftTabComplete(user, alias, args); - } - - - /** - * Instance of limits addon. - */ - private final Limits addon; - } - - - /** - * This material matches name to an entity type. - * @param name Name that must be matched. - * @return EntityType or null. - */ - @Nullable - private static EntityType matchEntity(String name) - { - String filtered = name; - - if (filtered.startsWith(NamespacedKey.MINECRAFT + ":")) - { - filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length()); - } - - filtered = filtered.toUpperCase(java.util.Locale.ENGLISH); - filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", ""); - - return Enums.getIfPresent(EntityType.class, filtered).orNull(); - } - - - /** - * This method crafts tab complete for all subcommands - * @param user User who runs command. - * @param alias Command alias. - * @param args List of args. - * @return Optional list of strings. - */ - private static Optional> craftTabComplete(User user, String alias, List args) - { - String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; - - if (args.isEmpty()) - { - // Don't show every player on the server. Require at least the first letter - return Optional.empty(); - } - else if (args.size() == 4) - { - List options = new ArrayList<>(Util.getOnlinePlayerList(user)); - return Optional.of(Util.tabLimit(options, lastArg)); - } - else if (args.size() == 5) - { - List options = Arrays.stream(Material.values()). - map(Enum::name). - collect(Collectors.toList()); - - options.addAll(Arrays.stream(EntityType.values()). - map(Enum::name). - collect(Collectors.toList())); - - return Optional.of(Util.tabLimit(options, lastArg)); - } - else - { - return Optional.empty(); - } - } -} +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.limits.commands.admin; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.common.base.Enums; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.Limits; +import world.bentobox.limits.objects.IslandBlockCount; + + +/** + * This command manages offsets to the player island limits. + */ +public class OffsetCommand extends CompositeCommand +{ + /** + * Instantiates a new Offset command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetCommand(Limits addon, CompositeCommand parent) + { + super(parent, "offset"); + + new OffsetSetCommand(addon, this); + new OffsetAddCommand(addon, this); + new OffsetRemoveCommand(addon, this); + new OffsetResetCommand(addon, this); + new OffsetDisplayCommand(addon, this); + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset"); + this.setParametersHelp("admin.limits.offset.parameters"); + this.setDescription("admin.limits.offset.description"); + } + + + @Override + public boolean execute(User user, String s, List list) + { + this.showHelp(this, user); + return true; + } + + + /** + * This command allows setting limit offset for material or entity. + */ + private static class OffsetSetCommand extends CompositeCommand + { + /** + * Instantiates a new Offset set command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetSetCommand(Limits addon, CompositeCommand parent) + { + super(parent, "set"); + this.addon = addon; + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset.set"); + this.setParametersHelp("admin.limits.offset.set.parameters"); + this.setDescription("admin.limits.offset.set.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 3) + { + // Show help + this.showHelp(this, user); + return false; + } + + // Get target player + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); + + // Get new offset + if (!Util.isInteger(args.get(2), true)) + { + user.sendMessage("general.errors.must-be-a-number", TextVariables.NUMBER, args.get(2)); + return false; + } + + Material material = Material.matchMaterial(args.get(1)); + EntityType entityType = matchEntity(args.get(1)); + + if (material == null && entityType == null) + { + user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); + return false; + } + + int offset = Integer.parseInt(args.get(2)); + + if (material != null && offset == islandData.getBlockLimitOffset(material) || + entityType != null && offset == islandData.getEntityLimitOffset(entityType)) + { + user.sendMessage("admin.limits.offset.set.same", + TextVariables.NAME, + args.get(1), + TextVariables.NUMBER, + args.get(2)); + return false; + } + + if (material != null) + { + islandData.setBlockLimitsOffset(material, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.set.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, material.name()); + } + else + { + islandData.setEntityLimitsOffset(entityType, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.set.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, entityType.name()); + } + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return OffsetCommand.craftTabComplete(user, alias, args); + } + + + /** + * Instance of limits addon. + */ + private final Limits addon; + } + + + /** + * This command allows increasing limit offset for material or entity. + */ + private static class OffsetAddCommand extends CompositeCommand + { + /** + * Instantiates a new Offset add command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetAddCommand(Limits addon, CompositeCommand parent) + { + super(parent, "add"); + this.addon = addon; + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset.add"); + this.setParametersHelp("admin.limits.offset.add.parameters"); + this.setDescription("admin.limits.offset.add.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 3) + { + // Show help + this.showHelp(this, user); + return false; + } + + // Get target player + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); + + // Get new offset + if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0) + { + user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2)); + return false; + } + + Material material = Material.matchMaterial(args.get(1)); + EntityType entityType = matchEntity(args.get(1)); + + if (material == null && entityType == null) + { + user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); + return false; + } + + int offset = Integer.parseInt(args.get(2)); + + if (material != null) + { + offset += islandData.getBlockLimitOffset(material); + + islandData.setBlockLimitsOffset(material, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.add.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, material.name()); + } + else + { + offset += islandData.getEntityLimitOffset(entityType); + + islandData.setEntityLimitsOffset(entityType, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.add.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, entityType.name()); + } + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return OffsetCommand.craftTabComplete(user, alias, args); + } + + + /** + * Instance of limits addon. + */ + private final Limits addon; + } + + + /** + * This command allows reducing limit offset for material or entity. + */ + private static class OffsetRemoveCommand extends CompositeCommand + { + /** + * Instantiates a new Offset remove command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetRemoveCommand(Limits addon, CompositeCommand parent) + { + super(parent, "remove"); + this.addon = addon; + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset.remove"); + this.setParametersHelp("admin.limits.offset.remove.parameters"); + this.setDescription("admin.limits.offset.remove.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 3) + { + // Show help + this.showHelp(this, user); + return false; + } + + // Get target player + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); + + // Get new offset + if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0) + { + user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2)); + return false; + } + + Material material = Material.matchMaterial(args.get(1)); + EntityType entityType = matchEntity(args.get(1)); + + if (material == null && entityType == null) + { + user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); + return false; + } + + int offset = Integer.parseInt(args.get(2)); + + if (material != null) + { + offset = islandData.getBlockLimitOffset(material) - offset; + + islandData.setBlockLimitsOffset(material, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.remove.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, material.name()); + } + else + { + offset = islandData.getEntityLimitOffset(entityType) - offset; + + islandData.setEntityLimitsOffset(entityType, offset); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.remove.success", + TextVariables.NUMBER, String.valueOf(offset), + TextVariables.NAME, entityType.name()); + } + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return OffsetCommand.craftTabComplete(user, alias, args); + } + + + /** + * Instance of limits addon. + */ + private final Limits addon; + } + + + /** + * This command allows resetting limit offset for material or entity. + */ + private static class OffsetResetCommand extends CompositeCommand + { + /** + * Instantiates a new Offset reset command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetResetCommand(Limits addon, CompositeCommand parent) + { + super(parent, "reset"); + this.addon = addon; + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset.reset"); + this.setParametersHelp("admin.limits.offset.reset.parameters"); + this.setDescription("admin.limits.offset.reset.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 2) + { + // Show help + this.showHelp(this, user); + return false; + } + + // Get target player + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); + + Material material = Material.matchMaterial(args.get(1)); + EntityType entityType = matchEntity(args.get(1)); + + if (material == null && entityType == null) + { + user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); + return false; + } + + if (material != null) + { + islandData.setBlockLimitsOffset(material, 0); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.reset.success", + TextVariables.NAME, material.name()); + } + else + { + islandData.setEntityLimitsOffset(entityType, 0); + islandData.setChanged(); + + user.sendMessage("admin.limits.offset.reset.success", + TextVariables.NAME, entityType.name()); + } + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return OffsetCommand.craftTabComplete(user, alias, args); + } + + + /** + * Instance of limits addon. + */ + private final Limits addon; + } + + + /** + * This command allows viewing limit offset for material or entity. + */ + private static class OffsetDisplayCommand extends CompositeCommand + { + /** + * Instantiates a new Offset display command. + * + * @param addon the addon + * @param parent the parent + */ + public OffsetDisplayCommand(Limits addon, CompositeCommand parent) + { + super(parent, "view", "display"); + this.addon = addon; + } + + + @Override + public void setup() + { + this.setOnlyPlayer(false); + + this.setPermission("admin.limits.offset.view"); + this.setParametersHelp("admin.limits.offset.view.parameters"); + this.setDescription("admin.limits.offset.view.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 2) + { + // Show help + this.showHelp(this, user); + return false; + } + + // Get target player + UUID targetUUID = Util.getUUID(args.get(0)); + + if (targetUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + Island island = this.getIslands().getIsland(this.getWorld(), targetUUID); + + if (island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island); + + Material material = Material.matchMaterial(args.get(1)); + EntityType entityType = matchEntity(args.get(1)); + + if (material == null && entityType == null) + { + user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1)); + return false; + } + + if (material != null) + { + int offset = islandData.getBlockLimitOffset(material); + user.sendMessage("admin.limits.offset.view.message", + TextVariables.NAME, material.name(), + TextVariables.NUMBER, String.valueOf(offset)); + } + else + { + int offset = islandData.getEntityLimitOffset(entityType); + user.sendMessage("admin.limits.offset.view.message", + TextVariables.NAME, entityType.name(), + TextVariables.NUMBER, String.valueOf(offset)); + } + + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return OffsetCommand.craftTabComplete(user, alias, args); + } + + + /** + * Instance of limits addon. + */ + private final Limits addon; + } + + + /** + * This material matches name to an entity type. + * @param name Name that must be matched. + * @return EntityType or null. + */ + @Nullable + private static EntityType matchEntity(String name) + { + String filtered = name; + + if (filtered.startsWith(NamespacedKey.MINECRAFT + ":")) + { + filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length()); + } + + filtered = filtered.toUpperCase(java.util.Locale.ENGLISH); + filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", ""); + + return Enums.getIfPresent(EntityType.class, filtered).orNull(); + } + + + /** + * This method crafts tab complete for all subcommands + * @param user User who runs command. + * @param alias Command alias. + * @param args List of args. + * @return Optional list of strings. + */ + private static Optional> craftTabComplete(User user, String alias, List args) + { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + + if (args.isEmpty()) + { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + else if (args.size() == 4) + { + List options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } + else if (args.size() == 5) + { + List options = Arrays.stream(Material.values()). + map(Enum::name). + collect(Collectors.toList()); + + options.addAll(Arrays.stream(EntityType.values()). + map(Enum::name). + collect(Collectors.toList())); + + return Optional.of(Util.tabLimit(options, lastArg)); + } + else + { + return Optional.empty(); + } + } +} diff --git a/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java b/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java index bb2d6e2..d0acf7d 100644 --- a/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java +++ b/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java @@ -1,78 +1,78 @@ -package world.bentobox.limits.commands.player; - -import java.util.Map; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.Limits; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * Shows a panel of the blocks that are limited and their status - * @author tastybento - * - */ -public class LimitPanel { - - private final Limits addon; - - /** - * @param addon - limit addon - */ - public LimitPanel(Limits addon) { - this.addon = addon; - } - - /** - * Show the limits panel - * @param gm - game mode - * @param user - user asking - * @param target - target uuid - */ - public void showLimits(GameModeAddon gm, User user, UUID target) { - // Get world - World world = gm.getOverWorld(); - // Get the island for the target - Island island = addon.getIslands().getIsland(world, target); - if (island == null) { - if (user.getUniqueId().equals(target)) { - user.sendMessage("general.errors.no-island"); - } else { - user.sendMessage("general.errors.player-has-no-island"); - } - return; - } - // See if the target is online - Player targetPlayer = Bukkit.getPlayer(target); - if (targetPlayer != null) { - // Update perms - addon.getJoinListener().checkPerms(targetPlayer, gm.getPermissionPrefix() + "island.limit.", island.getUniqueId(), gm.getDescription().getName()); - } - // Get the limits for this island - IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); - Map matLimits = addon.getBlockLimitListener().getMaterialLimits(world, island.getUniqueId()); - if (matLimits.isEmpty() && addon.getSettings().getLimits().isEmpty()) { - user.sendMessage("island.limits.no-limits"); - return; - } - - new TabbedPanelBuilder() - .user(user) - .world(world) - .tab(0, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.A2Z)) - .tab(1, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.Z2A)) - .startingSlot(0) - .size(54) - .build().openPanel(); - - } - -} +package world.bentobox.limits.commands.player; + +import java.util.Map; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.Limits; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * Shows a panel of the blocks that are limited and their status + * @author tastybento + * + */ +public class LimitPanel { + + private final Limits addon; + + /** + * @param addon - limit addon + */ + public LimitPanel(Limits addon) { + this.addon = addon; + } + + /** + * Show the limits panel + * @param gm - game mode + * @param user - user asking + * @param target - target uuid + */ + public void showLimits(GameModeAddon gm, User user, UUID target) { + // Get world + World world = gm.getOverWorld(); + // Get the island for the target + Island island = addon.getIslands().getIsland(world, target); + if (island == null) { + if (user.getUniqueId().equals(target)) { + user.sendMessage("general.errors.no-island"); + } else { + user.sendMessage("general.errors.player-has-no-island"); + } + return; + } + // See if the target is online + Player targetPlayer = Bukkit.getPlayer(target); + if (targetPlayer != null) { + // Update perms + addon.getJoinListener().checkPerms(targetPlayer, gm.getPermissionPrefix() + "island.limit.", island.getUniqueId(), gm.getDescription().getName()); + } + // Get the limits for this island + IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); + Map matLimits = addon.getBlockLimitListener().getMaterialLimits(world, island.getUniqueId()); + if (matLimits.isEmpty() && addon.getSettings().getLimits().isEmpty()) { + user.sendMessage("island.limits.no-limits"); + return; + } + + new TabbedPanelBuilder() + .user(user) + .world(world) + .tab(0, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.A2Z)) + .tab(1, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.Z2A)) + .startingSlot(0) + .size(54) + .build().openPanel(); + + } + +} diff --git a/src/main/java/world/bentobox/limits/commands/player/LimitTab.java b/src/main/java/world/bentobox/limits/commands/player/LimitTab.java index cf6a211..85199f8 100644 --- a/src/main/java/world/bentobox/limits/commands/player/LimitTab.java +++ b/src/main/java/world/bentobox/limits/commands/player/LimitTab.java @@ -1,269 +1,269 @@ -package world.bentobox.limits.commands.player; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.EntityType; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.collect.ImmutableMap; - -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.Tab; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.EntityGroup; -import world.bentobox.limits.Limits; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * @author tastybento - * - */ -public class LimitTab implements Tab { - - enum SORT_BY { - A2Z, - Z2A - } - - /** - * This maps the entity types to the icon that should be shown in the panel - * If the icon is null, then the entity type is not covered by the addon - */ - private static final Map E2M = ImmutableMap.builder() - .put(EntityType.MOOSHROOM, Material.MOOSHROOM_SPAWN_EGG).put(EntityType.SNOW_GOLEM, Material.SNOW_BLOCK) - .put(EntityType.IRON_GOLEM, Material.IRON_BLOCK) - .put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG) - .put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL) - //.put(EntityType.BOAT, Material.OAK_BOAT) - .put(EntityType.ARMOR_STAND, Material.ARMOR_STAND) - .put(EntityType.ITEM_FRAME, Material.ITEM_FRAME) - .put(EntityType.PAINTING, Material.PAINTING) - // Minecarts - .put(EntityType.TNT_MINECART, Material.TNT_MINECART).put(EntityType.CHEST_MINECART, Material.CHEST_MINECART) - .put(EntityType.COMMAND_BLOCK_MINECART, Material.COMMAND_BLOCK_MINECART) - .put(EntityType.FURNACE_MINECART, Material.FURNACE_MINECART) - .put(EntityType.HOPPER_MINECART, Material.HOPPER_MINECART) - .put(EntityType.SPAWNER_MINECART, Material.MINECART) - //.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT) - .build(); - // This is a map of blocks to Items - private static final Map B2M; - static { - ImmutableMap.Builder builder = ImmutableMap.builder() - .put(Material.POTATOES, Material.POTATO) - .put(Material.CARROTS, Material.CARROT) - .put(Material.BEETROOTS, Material.BEETROOT) - .put(Material.REDSTONE_WIRE, Material.REDSTONE).put(Material.MELON_STEM, Material.MELON) - .put(Material.PUMPKIN_STEM, Material.PUMPKIN); - // Block to Material icons - Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES")))); - Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO")))); - B2M = builder.build(); - } - - private final World world; - private final User user; - private final Limits addon; - private final List<@Nullable PanelItem> result; - private final SORT_BY sortBy; - - public LimitTab(Limits addon, IslandBlockCount ibc, Map matLimits, Island island, World world, User user, SORT_BY sortBy) { - this.addon = addon; - this.world = world; - this.user = user; - this.sortBy = sortBy; - result = new ArrayList<>(); - addMaterialIcons(ibc, matLimits); - addEntityLimits(ibc, island); - addEntityGroupLimits(ibc, island); - // Sort - if (sortBy == SORT_BY.Z2A) { - result.sort((o1, o2) -> o2.getName().compareTo(o1.getName())); - } else { - result.sort(Comparator.comparing(PanelItem::getName)); - } - - } - - private void addEntityGroupLimits(IslandBlockCount ibc, Island island) { - // Entity group limits - Map groupMap = addon.getSettings().getGroupLimitDefinitions().stream().collect(Collectors.toMap(e -> e, EntityGroup::getLimit)); - // Group by same loop up map - Map groupByName = groupMap.keySet().stream().collect(Collectors.toMap(EntityGroup::getName, e -> e)); - // Merge in any permission-based limits - if (ibc == null) { - return; - } - ibc.getEntityGroupLimits().entrySet().stream() - .filter(e -> groupByName.containsKey(e.getKey())) - .forEach(e -> groupMap.put(groupByName.get(e.getKey()), e.getValue())); - // Update the group map for each group limit offset. If the value already exists add it - ibc.getEntityGroupLimitsOffset().forEach((key, value) -> { - if (groupByName.get(key) != null) { - groupMap.put(groupByName.get(key), (groupMap.getOrDefault(groupByName.get(key), 0) + value)); - } - }); - groupMap.forEach((v, limit) -> { - PanelItemBuilder pib = new PanelItemBuilder(); - pib.name(user.getTranslation("island.limits.panel.entity-group-name-syntax", TextVariables.NAME, - v.getName())); - String description = ""; - description += "(" + prettyNames(v) + ")\n"; - pib.icon(v.getIcon()); - long count = getCount(island, v); - String color = count >= limit ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); - description += color - + user.getTranslation("island.limits.block-limit-syntax", - TextVariables.NUMBER, String.valueOf(count), - "[limit]", String.valueOf(limit)); - pib.description(description); - result.add(pib.build()); - }); - } - - private void addEntityLimits(IslandBlockCount ibc, Island island) { - // Entity limits - Map map = new HashMap<>(addon.getSettings().getLimits()); - // Merge in any permission-based limits - if (ibc != null) { - map.putAll(ibc.getEntityLimits()); - ibc.getEntityLimitsOffset().forEach((k,v) -> map.put(k, map.getOrDefault(k, 0) + v)); - } - - map.forEach((k,v) -> { - PanelItemBuilder pib = new PanelItemBuilder(); - pib.name(user.getTranslation("island.limits.panel.entity-name-syntax", TextVariables.NAME, - Util.prettifyText(k.toString()))); - Material m; - try { - if (E2M.containsKey(k)) { - m = E2M.get(k); - } else if (k.isAlive()) { - m = Material.valueOf(k + "_SPAWN_EGG"); - } else { - // Regular material - m = Material.valueOf(k.toString()); - } - } catch (Exception e) { - m = Material.BARRIER; - } - pib.icon(m); - long count = getCount(island, k); - String color = count >= v ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); - pib.description(color - + user.getTranslation("island.limits.block-limit-syntax", - TextVariables.NUMBER, String.valueOf(count), - "[limit]", String.valueOf(v))); - result.add(pib.build()); - }); - - } - - private void addMaterialIcons(IslandBlockCount ibc, Map matLimits) { - // Material limits - for (Entry en : matLimits.entrySet()) { - PanelItemBuilder pib = new PanelItemBuilder(); - pib.name(user.getTranslation("island.limits.panel.block-name-syntax", TextVariables.NAME, - Util.prettifyText(en.getKey().toString()))); - // Adjust icon - pib.icon(B2M.getOrDefault(en.getKey(), en.getKey())); - - int count = ibc == null ? 0 : ibc.getBlockCounts().getOrDefault(en.getKey(), 0); - int value = en.getValue(); - String color = count >= value ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); - pib.description(color - + user.getTranslation("island.limits.block-limit-syntax", - TextVariables.NUMBER, String.valueOf(count), - "[limit]", String.valueOf(value))); - result.add(pib.build()); - } - } - - @Override - public PanelItem getIcon() { - return new PanelItemBuilder().icon(Material.MAGENTA_GLAZED_TERRACOTTA).name(this.getName()).build(); - } - - @Override - public String getName() { - String sort = user.getTranslation(world, "island.limits.panel." + sortBy); - return user.getTranslation(world, "island.limits.panel.title-syntax", "[title]", - user.getTranslation(world, "limits.panel-title"), "[sort]", sort); - } - - @Override - public List<@Nullable PanelItem> getPanelItems() { - return result; - } - - @Override - public String getPermission() { - return ""; - } - - private String prettyNames(EntityGroup v) { - StringBuilder sb = new StringBuilder(); - List l = new ArrayList<>(v.getTypes()); - for(int i = 0; i < l.size(); i++) - { - sb.append(Util.prettifyText(l.get(i).toString())); - if (i + 1 < l.size()) - sb.append(", "); - if((i+1) % 5 == 0) - sb.append("\n"); - } - return sb.toString(); - } - - long getCount(Island island, EntityType ent) { - long count = island.getWorld().getEntities().stream() - .filter(e -> e.getType().equals(ent)) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - // Nether - if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) { - count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream() - .filter(e -> e.getType().equals(ent)) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - } - // End - if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) { - count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream() - .filter(e -> e.getType().equals(ent)) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - } - return count; - } - - long getCount(Island island, EntityGroup group) { - long count = island.getWorld().getEntities().stream() - .filter(e -> group.contains(e.getType())) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - // Nether - if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) { - count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream() - .filter(e -> group.contains(e.getType())) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - } - // End - if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) { - count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream() - .filter(e -> group.contains(e.getType())) - .filter(e -> island.inIslandSpace(e.getLocation())).count(); - } - return count; - } -} +package world.bentobox.limits.commands.player; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.common.collect.ImmutableMap; + +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.Tab; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.Limits; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * @author tastybento + * + */ +public class LimitTab implements Tab { + + enum SORT_BY { + A2Z, + Z2A + } + + /** + * This maps the entity types to the icon that should be shown in the panel + * If the icon is null, then the entity type is not covered by the addon + */ + private static final Map E2M = ImmutableMap.builder() + .put(EntityType.MOOSHROOM, Material.MOOSHROOM_SPAWN_EGG).put(EntityType.SNOW_GOLEM, Material.SNOW_BLOCK) + .put(EntityType.IRON_GOLEM, Material.IRON_BLOCK) + .put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG) + .put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL) + //.put(EntityType.BOAT, Material.OAK_BOAT) + .put(EntityType.ARMOR_STAND, Material.ARMOR_STAND) + .put(EntityType.ITEM_FRAME, Material.ITEM_FRAME) + .put(EntityType.PAINTING, Material.PAINTING) + // Minecarts + .put(EntityType.TNT_MINECART, Material.TNT_MINECART).put(EntityType.CHEST_MINECART, Material.CHEST_MINECART) + .put(EntityType.COMMAND_BLOCK_MINECART, Material.COMMAND_BLOCK_MINECART) + .put(EntityType.FURNACE_MINECART, Material.FURNACE_MINECART) + .put(EntityType.HOPPER_MINECART, Material.HOPPER_MINECART) + .put(EntityType.SPAWNER_MINECART, Material.MINECART) + //.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT) + .build(); + // This is a map of blocks to Items + private static final Map B2M; + static { + ImmutableMap.Builder builder = ImmutableMap.builder() + .put(Material.POTATOES, Material.POTATO) + .put(Material.CARROTS, Material.CARROT) + .put(Material.BEETROOTS, Material.BEETROOT) + .put(Material.REDSTONE_WIRE, Material.REDSTONE).put(Material.MELON_STEM, Material.MELON) + .put(Material.PUMPKIN_STEM, Material.PUMPKIN); + // Block to Material icons + Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES")))); + Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO")))); + B2M = builder.build(); + } + + private final World world; + private final User user; + private final Limits addon; + private final List<@Nullable PanelItem> result; + private final SORT_BY sortBy; + + public LimitTab(Limits addon, IslandBlockCount ibc, Map matLimits, Island island, World world, User user, SORT_BY sortBy) { + this.addon = addon; + this.world = world; + this.user = user; + this.sortBy = sortBy; + result = new ArrayList<>(); + addMaterialIcons(ibc, matLimits); + addEntityLimits(ibc, island); + addEntityGroupLimits(ibc, island); + // Sort + if (sortBy == SORT_BY.Z2A) { + result.sort((o1, o2) -> o2.getName().compareTo(o1.getName())); + } else { + result.sort(Comparator.comparing(PanelItem::getName)); + } + + } + + private void addEntityGroupLimits(IslandBlockCount ibc, Island island) { + // Entity group limits + Map groupMap = addon.getSettings().getGroupLimitDefinitions().stream().collect(Collectors.toMap(e -> e, EntityGroup::getLimit)); + // Group by same loop up map + Map groupByName = groupMap.keySet().stream().collect(Collectors.toMap(EntityGroup::getName, e -> e)); + // Merge in any permission-based limits + if (ibc == null) { + return; + } + ibc.getEntityGroupLimits().entrySet().stream() + .filter(e -> groupByName.containsKey(e.getKey())) + .forEach(e -> groupMap.put(groupByName.get(e.getKey()), e.getValue())); + // Update the group map for each group limit offset. If the value already exists add it + ibc.getEntityGroupLimitsOffset().forEach((key, value) -> { + if (groupByName.get(key) != null) { + groupMap.put(groupByName.get(key), (groupMap.getOrDefault(groupByName.get(key), 0) + value)); + } + }); + groupMap.forEach((v, limit) -> { + PanelItemBuilder pib = new PanelItemBuilder(); + pib.name(user.getTranslation("island.limits.panel.entity-group-name-syntax", TextVariables.NAME, + v.getName())); + String description = ""; + description += "(" + prettyNames(v) + ")\n"; + pib.icon(v.getIcon()); + long count = getCount(island, v); + String color = count >= limit ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); + description += color + + user.getTranslation("island.limits.block-limit-syntax", + TextVariables.NUMBER, String.valueOf(count), + "[limit]", String.valueOf(limit)); + pib.description(description); + result.add(pib.build()); + }); + } + + private void addEntityLimits(IslandBlockCount ibc, Island island) { + // Entity limits + Map map = new HashMap<>(addon.getSettings().getLimits()); + // Merge in any permission-based limits + if (ibc != null) { + map.putAll(ibc.getEntityLimits()); + ibc.getEntityLimitsOffset().forEach((k,v) -> map.put(k, map.getOrDefault(k, 0) + v)); + } + + map.forEach((k,v) -> { + PanelItemBuilder pib = new PanelItemBuilder(); + pib.name(user.getTranslation("island.limits.panel.entity-name-syntax", TextVariables.NAME, + Util.prettifyText(k.toString()))); + Material m; + try { + if (E2M.containsKey(k)) { + m = E2M.get(k); + } else if (k.isAlive()) { + m = Material.valueOf(k + "_SPAWN_EGG"); + } else { + // Regular material + m = Material.valueOf(k.toString()); + } + } catch (Exception e) { + m = Material.BARRIER; + } + pib.icon(m); + long count = getCount(island, k); + String color = count >= v ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); + pib.description(color + + user.getTranslation("island.limits.block-limit-syntax", + TextVariables.NUMBER, String.valueOf(count), + "[limit]", String.valueOf(v))); + result.add(pib.build()); + }); + + } + + private void addMaterialIcons(IslandBlockCount ibc, Map matLimits) { + // Material limits + for (Entry en : matLimits.entrySet()) { + PanelItemBuilder pib = new PanelItemBuilder(); + pib.name(user.getTranslation("island.limits.panel.block-name-syntax", TextVariables.NAME, + Util.prettifyText(en.getKey().toString()))); + // Adjust icon + pib.icon(B2M.getOrDefault(en.getKey(), en.getKey())); + + int count = ibc == null ? 0 : ibc.getBlockCounts().getOrDefault(en.getKey(), 0); + int value = en.getValue(); + String color = count >= value ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); + pib.description(color + + user.getTranslation("island.limits.block-limit-syntax", + TextVariables.NUMBER, String.valueOf(count), + "[limit]", String.valueOf(value))); + result.add(pib.build()); + } + } + + @Override + public PanelItem getIcon() { + return new PanelItemBuilder().icon(Material.MAGENTA_GLAZED_TERRACOTTA).name(this.getName()).build(); + } + + @Override + public String getName() { + String sort = user.getTranslation(world, "island.limits.panel." + sortBy); + return user.getTranslation(world, "island.limits.panel.title-syntax", "[title]", + user.getTranslation(world, "limits.panel-title"), "[sort]", sort); + } + + @Override + public List<@Nullable PanelItem> getPanelItems() { + return result; + } + + @Override + public String getPermission() { + return ""; + } + + private String prettyNames(EntityGroup v) { + StringBuilder sb = new StringBuilder(); + List l = new ArrayList<>(v.getTypes()); + for(int i = 0; i < l.size(); i++) + { + sb.append(Util.prettifyText(l.get(i).toString())); + if (i + 1 < l.size()) + sb.append(", "); + if((i+1) % 5 == 0) + sb.append("\n"); + } + return sb.toString(); + } + + long getCount(Island island, EntityType ent) { + long count = island.getWorld().getEntities().stream() + .filter(e -> e.getType().equals(ent)) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + // Nether + if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) { + count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream() + .filter(e -> e.getType().equals(ent)) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + } + // End + if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) { + count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream() + .filter(e -> e.getType().equals(ent)) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + } + return count; + } + + long getCount(Island island, EntityGroup group) { + long count = island.getWorld().getEntities().stream() + .filter(e -> group.contains(e.getType())) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + // Nether + if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) { + count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream() + .filter(e -> group.contains(e.getType())) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + } + // End + if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) { + count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream() + .filter(e -> group.contains(e.getType())) + .filter(e -> island.inIslandSpace(e.getLocation())).count(); + } + return count; + } +} diff --git a/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java b/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java index e0d9add..0031a35 100644 --- a/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java +++ b/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java @@ -1,71 +1,71 @@ -package world.bentobox.limits.commands.player; - -import java.util.List; -import java.util.Optional; - -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.Limits; - -/** - * User command for limits - * @author tastybento - * - */ -public class PlayerCommand extends CompositeCommand { - - private final Limits addon; - - /** - * Top level command - * @param addon - addon - */ - public PlayerCommand(Limits addon, CompositeCommand parent) { - super(parent, "limits"); - this.addon = addon; - new RecountCommand(addon, this); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() - */ - @Override - public void setup() { - this.setPermission("limits.player.limits"); - this.setOnlyPlayer(true); - this.setParametersHelp("island.limits.parameters"); - this.setDescription("island.limits.description"); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) - */ - @Override - public boolean execute(User user, String label, List args) { - if (!args.isEmpty()) { - showHelp(this, user); - return false; - } else { - // Report the limit for the island, which is governed by the owner of the island - Optional opIsland = getIslands().getIslandAt(user.getLocation()); - if (opIsland.isEmpty()) { - user.sendMessage("island.limits.errors.not-on-island"); - return false; - } - Island island = opIsland.get(); - if (!island.getWorld().equals(getWorld())) { - user.sendMessage("general.errors.wrong-world"); - return false; - } - if (island.getOwner() == null) { - user.sendMessage("island.limits.errors.no-owner"); - return false; - } - new LimitPanel(addon).showLimits((GameModeAddon) getAddon(), user, island.getOwner()); - return true; - } - } - -} +package world.bentobox.limits.commands.player; + +import java.util.List; +import java.util.Optional; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.Limits; + +/** + * User command for limits + * @author tastybento + * + */ +public class PlayerCommand extends CompositeCommand { + + private final Limits addon; + + /** + * Top level command + * @param addon - addon + */ + public PlayerCommand(Limits addon, CompositeCommand parent) { + super(parent, "limits"); + this.addon = addon; + new RecountCommand(addon, this); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() + */ + @Override + public void setup() { + this.setPermission("limits.player.limits"); + this.setOnlyPlayer(true); + this.setParametersHelp("island.limits.parameters"); + this.setDescription("island.limits.description"); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + */ + @Override + public boolean execute(User user, String label, List args) { + if (!args.isEmpty()) { + showHelp(this, user); + return false; + } else { + // Report the limit for the island, which is governed by the owner of the island + Optional opIsland = getIslands().getIslandAt(user.getLocation()); + if (opIsland.isEmpty()) { + user.sendMessage("island.limits.errors.not-on-island"); + return false; + } + Island island = opIsland.get(); + if (!island.getWorld().equals(getWorld())) { + user.sendMessage("general.errors.wrong-world"); + return false; + } + if (island.getOwner() == null) { + user.sendMessage("island.limits.errors.no-owner"); + return false; + } + new LimitPanel(addon).showLimits((GameModeAddon) getAddon(), user, island.getOwner()); + return true; + } + } + +} diff --git a/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java b/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java index 9f330b5..4b466b4 100644 --- a/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java +++ b/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java @@ -1,77 +1,77 @@ -package world.bentobox.limits.commands.player; - -import java.util.List; - -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.Limits; -import world.bentobox.limits.calculators.Pipeliner; - -/** - * - * @author tastybento - */ -public class RecountCommand extends CompositeCommand { - - private final Limits addon; - private @Nullable Island island; - - /** - * Player command to do a recount. Has a cooldown - * - * @param addon - addon - */ - public RecountCommand(Limits addon, CompositeCommand parent) { - super(parent, "recount"); - this.addon = addon; - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() - */ - @Override - public void setup() { - this.setPermission("limits.player.recount"); - this.setOnlyPlayer(true); - this.setParametersHelp("island.limits.recount.parameters"); - this.setDescription("island.limits.recount.description"); - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) - */ - @Override - public boolean canExecute(User user, String label, List args) { - if (!args.isEmpty()) { - showHelp(this, user); - return false; - } - island = addon.getIslands().getIsland(getWorld(), user); - if (island == null) { - user.sendMessage("general.errors.no-island"); - return false; - } - return !checkCooldown(user); - } - @Override - public boolean execute(User user, String label, List args) { - // Set cooldown - setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120)); - user.sendMessage("island.limits.recount.now-recounting"); - new Pipeliner(addon).addIsland(island).thenAccept(results -> { - if (results == null) { - user.sendMessage("island.limits.recount.in-progress"); - } else { - switch (results.getState()) { - case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout"); - default -> user.sendMessage("admin.limits.calc.finished"); - } - } - }); - return true; - } - -} +package world.bentobox.limits.commands.player; + +import java.util.List; + +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.Limits; +import world.bentobox.limits.calculators.Pipeliner; + +/** + * + * @author tastybento + */ +public class RecountCommand extends CompositeCommand { + + private final Limits addon; + private @Nullable Island island; + + /** + * Player command to do a recount. Has a cooldown + * + * @param addon - addon + */ + public RecountCommand(Limits addon, CompositeCommand parent) { + super(parent, "recount"); + this.addon = addon; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() + */ + @Override + public void setup() { + this.setPermission("limits.player.recount"); + this.setOnlyPlayer(true); + this.setParametersHelp("island.limits.recount.parameters"); + this.setDescription("island.limits.recount.description"); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + */ + @Override + public boolean canExecute(User user, String label, List args) { + if (!args.isEmpty()) { + showHelp(this, user); + return false; + } + island = addon.getIslands().getIsland(getWorld(), user); + if (island == null) { + user.sendMessage("general.errors.no-island"); + return false; + } + return !checkCooldown(user); + } + @Override + public boolean execute(User user, String label, List args) { + // Set cooldown + setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120)); + user.sendMessage("island.limits.recount.now-recounting"); + new Pipeliner(addon).addIsland(island).thenAccept(results -> { + if (results == null) { + user.sendMessage("island.limits.recount.in-progress"); + } else { + switch (results.getState()) { + case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout"); + default -> user.sendMessage("admin.limits.calc.finished"); + } + } + }); + return true; + } + +} diff --git a/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java b/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java index b156020..a1a03d9 100644 --- a/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java +++ b/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java @@ -1,121 +1,121 @@ -package world.bentobox.limits.events; - -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.HandlerList; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.events.BentoBoxEvent; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * Fired when a player joins the server and before limit settings for their island are changed based - * on the player's permissions. If cancelled, no limit settings will be made. - * @author tastybento - * - */ -public class LimitsJoinPermCheckEvent extends BentoBoxEvent implements Cancellable { - - private final Player player; - private final String islandId; - private IslandBlockCount ibc; - private boolean cancel; - private boolean ignorePerms; - private static final HandlerList handlers = new HandlerList(); - - @Override - public @NonNull HandlerList getHandlers() { - return getHandlerList(); - } - - public static HandlerList getHandlerList() { - return handlers; - } - - /** - * Fired when a player joins the server and before limit settings for their island are changed based - * on the player's permissions. If cancelled, no limit settings will be made. - * @param player - player joining - * @param islandId - the unique island id. - * @param ibc - IslandBlockCount object for this island - */ - public LimitsJoinPermCheckEvent(@NonNull Player player, @NonNull String islandId, @Nullable IslandBlockCount ibc) { - super(); - this.player = player; - this.islandId = islandId; - this.ibc = ibc; - } - - - /** - * Get the player joining - * @return the player - */ - @NonNull - public Player getPlayer() { - return player; - } - - - /** - * Get the unique island id. Use the islands manager to obtain the island - * @return the islandId - */ - @NonNull - public String getIslandId() { - return islandId; - } - - - /** - * Get the island block count - * @return the ibc - */ - @Nullable - public IslandBlockCount getIbc() { - return ibc; - } - - - /** - * Set the island block count to a specific setting - * @param ibc the ibc to set - */ - public void setIbc(@Nullable IslandBlockCount ibc) { - this.ibc = ibc; - } - - - @Override - public boolean isCancelled() { - return cancel; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancel = cancel; - - } - - - /** - * Check if player's perms should be considered or not - * @return the ignorePerms - */ - public boolean isIgnorePerms() { - return ignorePerms; - } - - - /** - * Ignore player's perms. This differs to canceling the event in that the IslandBlockCount will be used if given via - * {@link #setIbc(IslandBlockCount ibc)} - * @param ignorePerms the ignorePerms to set - */ - public void setIgnorePerms(boolean ignorePerms) { - this.ignorePerms = ignorePerms; - } - - -} +package world.bentobox.limits.events; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.events.BentoBoxEvent; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * Fired when a player joins the server and before limit settings for their island are changed based + * on the player's permissions. If cancelled, no limit settings will be made. + * @author tastybento + * + */ +public class LimitsJoinPermCheckEvent extends BentoBoxEvent implements Cancellable { + + private final Player player; + private final String islandId; + private IslandBlockCount ibc; + private boolean cancel; + private boolean ignorePerms; + private static final HandlerList handlers = new HandlerList(); + + @Override + public @NonNull HandlerList getHandlers() { + return getHandlerList(); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Fired when a player joins the server and before limit settings for their island are changed based + * on the player's permissions. If cancelled, no limit settings will be made. + * @param player - player joining + * @param islandId - the unique island id. + * @param ibc - IslandBlockCount object for this island + */ + public LimitsJoinPermCheckEvent(@NonNull Player player, @NonNull String islandId, @Nullable IslandBlockCount ibc) { + super(); + this.player = player; + this.islandId = islandId; + this.ibc = ibc; + } + + + /** + * Get the player joining + * @return the player + */ + @NonNull + public Player getPlayer() { + return player; + } + + + /** + * Get the unique island id. Use the islands manager to obtain the island + * @return the islandId + */ + @NonNull + public String getIslandId() { + return islandId; + } + + + /** + * Get the island block count + * @return the ibc + */ + @Nullable + public IslandBlockCount getIbc() { + return ibc; + } + + + /** + * Set the island block count to a specific setting + * @param ibc the ibc to set + */ + public void setIbc(@Nullable IslandBlockCount ibc) { + this.ibc = ibc; + } + + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + + } + + + /** + * Check if player's perms should be considered or not + * @return the ignorePerms + */ + public boolean isIgnorePerms() { + return ignorePerms; + } + + + /** + * Ignore player's perms. This differs to canceling the event in that the IslandBlockCount will be used if given via + * {@link #setIbc(IslandBlockCount ibc)} + * @param ignorePerms the ignorePerms to set + */ + public void setIgnorePerms(boolean ignorePerms) { + this.ignorePerms = ignorePerms; + } + + +} diff --git a/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java b/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java index 17d40e8..e0c6e25 100644 --- a/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java +++ b/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java @@ -1,116 +1,116 @@ -package world.bentobox.limits.events; - -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.limits.EntityGroup; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * Fired when a player joins the server and for each perm-based limit setting. - * If cancelled, no limit settings will be made. - * Settings can be adjusted and will be used. - * @author tastybento - * - */ -public class LimitsPermCheckEvent extends LimitsJoinPermCheckEvent { - - private @Nullable EntityGroup entityGroup; - private @Nullable EntityType entityType; - private @Nullable Material material; - private int value; - - /** - * Fired when a player joins the server and for each perm-based limit setting. - * If cancelled, no limit settings will be made. - * Settings can be adjusted and will be used. - * @param player - player joining - * @param islandId - the unique island id. - * @param ibc - IslandBlockCount object for this island - * @param material - material being limited, or null - * @param entityType - entity type being limited, or null - * @param entgroup - entity group being limited, or null - * @param value - numeric limit given by the perm - */ - public LimitsPermCheckEvent(@NonNull Player player, - @NonNull String islandId, - @Nullable IslandBlockCount ibc, - @Nullable EntityGroup entgroup, - @Nullable EntityType entityType, - @Nullable Material material, - int value) { - super(player, islandId, ibc); - this.entityGroup = entgroup; - this.entityType = entityType; - this.material = material; - this.value = value; - } - - /** - * @return the entityGroup - */ - public @Nullable EntityGroup getEntityGroup() { - return entityGroup; - } - - - /** - * @param entityGroup the entityGroup to set - */ - public void setEntityGroup(@Nullable EntityGroup entityGroup) { - this.entityGroup = entityGroup; - } - - - /** - * @return the entityType - */ - public @Nullable EntityType getEntityType() { - return entityType; - } - - - /** - * @param entityType the entityType to set - */ - public void setEntityType(@Nullable EntityType entityType) { - this.entityType = entityType; - } - - - /** - * @return the material - */ - public @Nullable Material getMaterial() { - return material; - } - - - /** - * @param material the material to set - */ - public void setMaterial(@Nullable Material material) { - this.material = material; - } - - - /** - * @return the value - */ - public int getValue() { - return value; - } - - - /** - * @param value the value to set - */ - public void setValue(int value) { - this.value = value; - } - - -} +package world.bentobox.limits.events; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * Fired when a player joins the server and for each perm-based limit setting. + * If cancelled, no limit settings will be made. + * Settings can be adjusted and will be used. + * @author tastybento + * + */ +public class LimitsPermCheckEvent extends LimitsJoinPermCheckEvent { + + private @Nullable EntityGroup entityGroup; + private @Nullable EntityType entityType; + private @Nullable Material material; + private int value; + + /** + * Fired when a player joins the server and for each perm-based limit setting. + * If cancelled, no limit settings will be made. + * Settings can be adjusted and will be used. + * @param player - player joining + * @param islandId - the unique island id. + * @param ibc - IslandBlockCount object for this island + * @param material - material being limited, or null + * @param entityType - entity type being limited, or null + * @param entgroup - entity group being limited, or null + * @param value - numeric limit given by the perm + */ + public LimitsPermCheckEvent(@NonNull Player player, + @NonNull String islandId, + @Nullable IslandBlockCount ibc, + @Nullable EntityGroup entgroup, + @Nullable EntityType entityType, + @Nullable Material material, + int value) { + super(player, islandId, ibc); + this.entityGroup = entgroup; + this.entityType = entityType; + this.material = material; + this.value = value; + } + + /** + * @return the entityGroup + */ + public @Nullable EntityGroup getEntityGroup() { + return entityGroup; + } + + + /** + * @param entityGroup the entityGroup to set + */ + public void setEntityGroup(@Nullable EntityGroup entityGroup) { + this.entityGroup = entityGroup; + } + + + /** + * @return the entityType + */ + public @Nullable EntityType getEntityType() { + return entityType; + } + + + /** + * @param entityType the entityType to set + */ + public void setEntityType(@Nullable EntityType entityType) { + this.entityType = entityType; + } + + + /** + * @return the material + */ + public @Nullable Material getMaterial() { + return material; + } + + + /** + * @param material the material to set + */ + public void setMaterial(@Nullable Material material) { + this.material = material; + } + + + /** + * @return the value + */ + public int getValue() { + return value; + } + + + /** + * @param value the value to set + */ + public void setValue(int value) { + this.value = value; + } + + +} diff --git a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java index 35e2ead..0103274 100644 --- a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java +++ b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java @@ -1,501 +1,506 @@ -package world.bentobox.limits.listeners; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.TechnicalPiston; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -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.BlockBurnEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockFadeEvent; -import org.bukkit.event.block.BlockFormEvent; -import org.bukkit.event.block.BlockFromToEvent; -import org.bukkit.event.block.BlockGrowEvent; -import org.bukkit.event.block.BlockMultiPlaceEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.event.block.EntityBlockFormEvent; -import org.bukkit.event.block.LeavesDecayEvent; -import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.Database; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.Limits; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * @author tastybento - * - */ -public class BlockLimitsListener implements Listener { - - /** - * Blocks that are not counted - */ - private static final List DO_NOT_COUNT = Arrays.asList(Material.LAVA, Material.WATER, Material.AIR, Material.FIRE, Material.END_PORTAL, Material.NETHER_PORTAL); - private static final List STACKABLE; - - static { - List stackable = new ArrayList<>(); - stackable.add(Material.SUGAR_CANE); - Optional.ofNullable(Material.getMaterial("BAMBOO")).ifPresent(stackable::add); - STACKABLE = Collections.unmodifiableList(stackable); - } - - /** - * Save every 10 blocks of change - */ - private static final Integer CHANGE_LIMIT = 9; - private final Limits addon; - private final Map islandCountMap = new HashMap<>(); - private final Map saveMap = new HashMap<>(); - private final Database handler; - private final Map> worldLimitMap = new HashMap<>(); - private Map defaultLimitMap = new EnumMap<>(Material.class); - - public BlockLimitsListener(Limits addon) { - this.addon = addon; - handler = new Database<>(addon, IslandBlockCount.class); - List toBeDeleted = new ArrayList<>(); - handler.loadObjects().forEach(ibc -> { - // Clean up - if (addon.isCoveredGameMode(ibc.getGameMode())) { - ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains); - // Store - islandCountMap.put(ibc.getUniqueId(), ibc); - } else { - toBeDeleted.add(ibc.getUniqueId()); - } - }); - toBeDeleted.forEach(handler::deleteID); - loadAllLimits(); - } - - /** - * Loads the default and world-specific limits - */ - private void loadAllLimits() { - // Load the default limits - addon.log("Loading default limits"); - if (addon.getConfig().isConfigurationSection("blocklimits")) { - ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits"); - defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig)); - } - - // Load specific worlds - if (addon.getConfig().isConfigurationSection("worlds")) { - ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds"); - for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) { - World world = Bukkit.getWorld(worldName); - if (world != null && addon.inGameModeWorld(world)) { - addon.log("Loading limits for " + world.getName()); - worldLimitMap.putIfAbsent(world, new HashMap<>()); - ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName); - worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig))); - } - } - } - - } - - /** - * Loads limit map from configuration section - * - * @param cs - configuration section - * @return limit map - */ - private Map loadLimits(ConfigurationSection cs) { - Map mats = new EnumMap<>(Material.class); - for (String material : cs.getKeys(false)) { - Material mat = Material.getMaterial(material); - if (mat != null && mat.isBlock() && !DO_NOT_COUNT.contains(mat)) { - mats.put(mat, cs.getInt(material)); - addon.log("Limit " + mat + " to " + cs.getInt(material)); - } else { - addon.logError("Material " + material + " is not a valid block. Skipping..."); - } - } - return mats; - } - - - /** - * Save the count database completely - */ - public void save() { - islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync); - } - - // Player-related events - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockPlaceEvent e) { - notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType()); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockBreakEvent e) { - handleBreak(e, e.getBlock()); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onTurtleEggBreak(PlayerInteractEvent e) { - if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) { - handleBreak(e, e.getClickedBlock()); - } - } - - private void handleBreak(Event e, Block b) { - if (!addon.inGameModeWorld(b.getWorld())) { - return; - } - Material mat = b.getType(); - // Check for stackable plants - if (STACKABLE.contains(b.getType())) { - // Check for blocks above - Block block = b; - while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) { - block = block.getRelative(BlockFace.UP); - process(block, false); - } - } - process(b, false); - // Player breaks a block and there was a redstone dust/repeater/... above - if (b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_WIRE || b.getRelative(BlockFace.UP).getType() == Material.REPEATER || b.getRelative(BlockFace.UP).getType() == Material.COMPARATOR || b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_TORCH) { - process(b.getRelative(BlockFace.UP), false); - } - if (b.getRelative(BlockFace.EAST).getType() == Material.REDSTONE_WALL_TORCH) { - process(b.getRelative(BlockFace.EAST), false); - } - if (b.getRelative(BlockFace.WEST).getType() == Material.REDSTONE_WALL_TORCH) { - process(b.getRelative(BlockFace.WEST), false); - } - if (b.getRelative(BlockFace.SOUTH).getType() == Material.REDSTONE_WALL_TORCH) { - process(b.getRelative(BlockFace.SOUTH), false); - } - if (b.getRelative(BlockFace.NORTH).getType() == Material.REDSTONE_WALL_TORCH) { - process(b.getRelative(BlockFace.NORTH), false); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockMultiPlaceEvent e) { - notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType()); - } - - /** - * Cancel the event and notify the user of failure - * @param e event - * @param user user - * @param limit maximum limit allowed - * @param m material - */ - private void notify(Cancellable e, User user, int limit, Material m) { - if (limit > -1) { - user.notify("block-limits.hit-limit", - "[material]", Util.prettifyText(m.toString()), - TextVariables.NUMBER, String.valueOf(limit)); - e.setCancelled(true); - } - } - - // Non-player events - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockBurnEvent e) { - process(e.getBlock(), false); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockExplodeEvent e) { - e.blockList().forEach(b -> process(b, false)); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockFadeEvent e) { - process(e.getBlock(), false); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockFormEvent e) { - process(e.getBlock(), true); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockSpreadEvent e) { - process(e.getBlock(), true); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(EntityBlockFormEvent e) { - process(e.getBlock(), true); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockGrowEvent e) { - if (process(e.getNewState().getBlock(), true) > -1) { - e.setCancelled(true); - e.getBlock().getWorld().getBlockAt(e.getBlock().getLocation()).setBlockData(e.getBlock().getBlockData()); - } else { - process(e.getBlock(), false); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(LeavesDecayEvent e) { - process(e.getBlock(), false); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(EntityExplodeEvent e) { - e.blockList().forEach(b -> process(b, false)); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(EntityChangeBlockEvent e) { - process(e.getBlock(), false); - if (e.getBlock().getType().equals(Material.FARMLAND)) { - process(e.getBlock().getRelative(BlockFace.UP), false); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlock(BlockFromToEvent e) { - if (e.getBlock().isLiquid() - && (e.getToBlock().getType() == Material.REDSTONE_WIRE - || e.getToBlock().getType() == Material.REPEATER - || e.getToBlock().getType() == Material.COMPARATOR - || e.getToBlock().getType() == Material.REDSTONE_TORCH - || e.getToBlock().getType() == Material.REDSTONE_WALL_TORCH)) { - process(e.getToBlock(), false); - } - } - - /** - * Return equivalents. Maps things like wall materials to their non-wall equivalents - * @param b block data - * @return material that matches the block data - */ - public Material fixMaterial(BlockData b) { - Material mat = b.getMaterial(); - - if (mat == Material.REDSTONE_WALL_TORCH) { - return Material.REDSTONE_TORCH; - } else if (mat == Material.WALL_TORCH) { - return Material.TORCH; - } else if (mat == Material.ZOMBIE_WALL_HEAD) { - return Material.ZOMBIE_HEAD; - } else if (mat == Material.CREEPER_WALL_HEAD) { - return Material.CREEPER_HEAD; - } else if (mat == Material.PLAYER_WALL_HEAD) { - return Material.PLAYER_HEAD; - } else if (mat == Material.DRAGON_WALL_HEAD) { - return Material.DRAGON_HEAD; - } else if (mat == Material.BAMBOO_SAPLING) { - return Material.BAMBOO; - } else if (mat == Material.PISTON_HEAD || mat == Material.MOVING_PISTON) { - TechnicalPiston tp = (TechnicalPiston) b; - if (tp.getType() == TechnicalPiston.Type.NORMAL) { - return Material.PISTON; - } else { - return Material.STICKY_PISTON; - } - } - return mat; - } - - /** - * Check if a block can be - * - * @param b - block - * @param add - true to add a block, false to remove - * @return limit amount if over limit, or -1 if no limitation - */ - private int process(Block b, boolean add) { - if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) { - return -1; - } - // Check if on island - return addon.getIslands().getIslandAt(b.getLocation()).map(i -> { - String id = i.getUniqueId(); - String gameMode = addon.getGameModeName(b.getWorld()); - if (gameMode.isEmpty()) { - // Invalid world - return -1; - } - // Ignore the center block - usually bedrock, but for AOneBlock it's the magic block - if (addon.getConfig().getBoolean("ignore-center-block", true) && i.getCenter().equals(b.getLocation())) { - return -1; - } - islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode)); - if (add) { - // Check limit - int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id); - if (limit > -1) { - return limit; - } - islandCountMap.get(id).add(fixMaterial(b.getBlockData())); - } else { - if (islandCountMap.containsKey(id)) { - islandCountMap.get(id).remove(fixMaterial(b.getBlockData())); - } - } - updateSaveMap(id); - return -1; - }).orElse(-1); - } - - /** - * Removed a block from any island limit count - * @param b - block to remove - */ - public void removeBlock(Block b) { - // Get island - addon.getIslands().getIslandAt(b.getLocation()).ifPresent(i -> { - String id = i.getUniqueId(); - String gameMode = addon.getGameModeName(b.getWorld()); - if (gameMode.isEmpty()) { - // Invalid world - return; - } - islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(fixMaterial(b.getBlockData())); - updateSaveMap(id); - }); - } - private void updateSaveMap(String id) { - saveMap.putIfAbsent(id, 0); - if (saveMap.merge(id, 1, Integer::sum) > CHANGE_LIMIT) { - handler.saveObjectAsync(islandCountMap.get(id)); - saveMap.remove(id); - } - } - - - /** - * Check if this material is at its limit for world on this island - * - * @param w - world - * @param m - material - * @param id - island id - * @return limit amount if at limit or -1 if no limit - */ - private int checkLimit(World w, Material m, String id) { - // Check island limits - IslandBlockCount ibc = islandCountMap.get(id); - if (ibc.isBlockLimited(m)) { - return ibc.isAtLimit(m) ? ibc.getBlockLimit(m) + ibc.getBlockLimitOffset(m) : -1; - } - // Check specific world limits - if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) { - // Material is overridden in world - return ibc.isAtLimit(m, worldLimitMap.get(w).get(m)) ? worldLimitMap.get(w).get(m) + ibc.getBlockLimitOffset(m) : -1; - } - // Check default limit map - if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) { - return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m); - } - // No limit - return -1; - } - - /** - * Gets an aggregate map of the limits for this island - * - * @param w - world - * @param id - island id - * @return map of limits for materials - */ - public Map getMaterialLimits(World w, String id) { - // Merge limits - Map result = new EnumMap<>(Material.class); - // Default - result.putAll(defaultLimitMap); - // World - if (worldLimitMap.containsKey(w)) { - result.putAll(worldLimitMap.get(w)); - } - // Island - if (islandCountMap.containsKey(id)) { - IslandBlockCount islandBlockCount = islandCountMap.get(id); - result.putAll(islandBlockCount.getBlockLimits()); - - // Add offsets to the every limit. - islandBlockCount.getBlockLimitsOffset().forEach((material, offset) -> - result.put(material, result.getOrDefault(material, 0) + offset)); - } - return result; - } - - /** - * Removes island from the database - * - * @param e - island delete event - */ - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onIslandDelete(IslandDeleteEvent e) { - islandCountMap.remove(e.getIsland().getUniqueId()); - saveMap.remove(e.getIsland().getUniqueId()); - if (handler.objectExists(e.getIsland().getUniqueId())) { - handler.deleteID(e.getIsland().getUniqueId()); - } - } - - /** - * Set the island block count values - * - * @param islandId - island unique id - * @param ibc - island block count - */ - public void setIsland(String islandId, IslandBlockCount ibc) { - islandCountMap.put(islandId, ibc); - handler.saveObjectAsync(ibc); - } - - /** - * Get the island block count - * - * @param islandId - island unique id - * @return island block count or null if there is none yet - */ - @Nullable - public IslandBlockCount getIsland(String islandId) { - return islandCountMap.get(islandId); - } - - /** - * Get the island block count for island and make one if it does not exist - * @param island island - * @return island block count - */ - @NonNull - public IslandBlockCount getIsland(Island island) { - return islandCountMap.computeIfAbsent(island.getUniqueId(), k -> new IslandBlockCount(k, island.getGameMode())); - } - -} +package world.bentobox.limits.listeners; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.TechnicalPiston; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +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.BlockBurnEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.block.BlockMultiPlaceEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockSpreadEvent; +import org.bukkit.event.block.EntityBlockFormEvent; +import org.bukkit.event.block.LeavesDecayEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.Database; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.Limits; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * @author tastybento + * + */ +public class BlockLimitsListener implements Listener { + + /** + * Blocks that are not counted + */ + private static final List DO_NOT_COUNT = Arrays.asList(Material.LAVA, Material.WATER, Material.AIR, Material.FIRE, Material.END_PORTAL, Material.NETHER_PORTAL); + private static final List STACKABLE; + + static { + List stackable = new ArrayList<>(); + stackable.add(Material.SUGAR_CANE); + Optional.ofNullable(Material.getMaterial("BAMBOO")).ifPresent(stackable::add); + STACKABLE = Collections.unmodifiableList(stackable); + } + + /** + * Save every 10 blocks of change + */ + private static final Integer CHANGE_LIMIT = 9; + private final Limits addon; + private final Map islandCountMap = new HashMap<>(); + private final Map saveMap = new HashMap<>(); + private final Database handler; + private final Map> worldLimitMap = new HashMap<>(); + private Map defaultLimitMap = new EnumMap<>(Material.class); + + public BlockLimitsListener(Limits addon) { + this.addon = addon; + handler = new Database<>(addon, IslandBlockCount.class); + List toBeDeleted = new ArrayList<>(); + handler.loadObjects().forEach(ibc -> { + // Clean up + if (addon.isCoveredGameMode(ibc.getGameMode())) { + ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains); + // Store + islandCountMap.put(ibc.getUniqueId(), ibc); + } else { + toBeDeleted.add(ibc.getUniqueId()); + } + }); + toBeDeleted.forEach(handler::deleteID); + loadAllLimits(); + } + + /** + * Loads the default and world-specific limits + */ + private void loadAllLimits() { + // Load the default limits + addon.log("Loading default limits"); + if (addon.getConfig().isConfigurationSection("blocklimits")) { + ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits"); + defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig)); + } + + // Load specific worlds + if (addon.getConfig().isConfigurationSection("worlds")) { + ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds"); + for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) { + World world = Bukkit.getWorld(worldName); + if (world != null && addon.inGameModeWorld(world)) { + addon.log("Loading limits for " + world.getName()); + worldLimitMap.putIfAbsent(world, new HashMap<>()); + ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName); + worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig))); + } + } + } + + } + + /** + * Loads limit map from configuration section + * + * @param cs - configuration section + * @return limit map + */ + private Map loadLimits(ConfigurationSection cs) { + Map mats = new EnumMap<>(Material.class); + for (String material : cs.getKeys(false)) { + Material mat = Material.getMaterial(material); + if (mat != null && mat.isBlock() && !DO_NOT_COUNT.contains(mat)) { + mats.put(mat, cs.getInt(material)); + addon.log("Limit " + mat + " to " + cs.getInt(material)); + } else { + addon.logError("Material " + material + " is not a valid block. Skipping..."); + } + } + return mats; + } + + + /** + * Save the count database completely + */ + public void save() { + islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync); + } + + // Player-related events + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockPlaceEvent e) { + notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockBreakEvent e) { + if (e.getBlock().hasMetadata("blockbreakevent-ignore")) { + // Ignore event due to Advanced Enchantments. See https://ae.advancedplugins.net/for-developers/plugin-compatiblity-issues + // @since 1.28.0 + return; + } + handleBreak(e, e.getBlock()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onTurtleEggBreak(PlayerInteractEvent e) { + if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) { + handleBreak(e, e.getClickedBlock()); + } + } + + private void handleBreak(Event e, Block b) { + if (!addon.inGameModeWorld(b.getWorld())) { + return; + } + Material mat = b.getType(); + // Check for stackable plants + if (STACKABLE.contains(b.getType())) { + // Check for blocks above + Block block = b; + while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) { + block = block.getRelative(BlockFace.UP); + process(block, false); + } + } + process(b, false); + // Player breaks a block and there was a redstone dust/repeater/... above + if (b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_WIRE || b.getRelative(BlockFace.UP).getType() == Material.REPEATER || b.getRelative(BlockFace.UP).getType() == Material.COMPARATOR || b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_TORCH) { + process(b.getRelative(BlockFace.UP), false); + } + if (b.getRelative(BlockFace.EAST).getType() == Material.REDSTONE_WALL_TORCH) { + process(b.getRelative(BlockFace.EAST), false); + } + if (b.getRelative(BlockFace.WEST).getType() == Material.REDSTONE_WALL_TORCH) { + process(b.getRelative(BlockFace.WEST), false); + } + if (b.getRelative(BlockFace.SOUTH).getType() == Material.REDSTONE_WALL_TORCH) { + process(b.getRelative(BlockFace.SOUTH), false); + } + if (b.getRelative(BlockFace.NORTH).getType() == Material.REDSTONE_WALL_TORCH) { + process(b.getRelative(BlockFace.NORTH), false); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockMultiPlaceEvent e) { + notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType()); + } + + /** + * Cancel the event and notify the user of failure + * @param e event + * @param user user + * @param limit maximum limit allowed + * @param m material + */ + private void notify(Cancellable e, User user, int limit, Material m) { + if (limit > -1) { + user.notify("block-limits.hit-limit", + "[material]", Util.prettifyText(m.toString()), + TextVariables.NUMBER, String.valueOf(limit)); + e.setCancelled(true); + } + } + + // Non-player events + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockBurnEvent e) { + process(e.getBlock(), false); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockExplodeEvent e) { + e.blockList().forEach(b -> process(b, false)); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockFadeEvent e) { + process(e.getBlock(), false); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockFormEvent e) { + process(e.getBlock(), true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockSpreadEvent e) { + process(e.getBlock(), true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(EntityBlockFormEvent e) { + process(e.getBlock(), true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockGrowEvent e) { + if (process(e.getNewState().getBlock(), true) > -1) { + e.setCancelled(true); + e.getBlock().getWorld().getBlockAt(e.getBlock().getLocation()).setBlockData(e.getBlock().getBlockData()); + } else { + process(e.getBlock(), false); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(LeavesDecayEvent e) { + process(e.getBlock(), false); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(EntityExplodeEvent e) { + e.blockList().forEach(b -> process(b, false)); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(EntityChangeBlockEvent e) { + process(e.getBlock(), false); + if (e.getBlock().getType().equals(Material.FARMLAND)) { + process(e.getBlock().getRelative(BlockFace.UP), false); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlock(BlockFromToEvent e) { + if (e.getBlock().isLiquid() + && (e.getToBlock().getType() == Material.REDSTONE_WIRE + || e.getToBlock().getType() == Material.REPEATER + || e.getToBlock().getType() == Material.COMPARATOR + || e.getToBlock().getType() == Material.REDSTONE_TORCH + || e.getToBlock().getType() == Material.REDSTONE_WALL_TORCH)) { + process(e.getToBlock(), false); + } + } + + /** + * Return equivalents. Maps things like wall materials to their non-wall equivalents + * @param b block data + * @return material that matches the block data + */ + public Material fixMaterial(BlockData b) { + Material mat = b.getMaterial(); + + if (mat == Material.REDSTONE_WALL_TORCH) { + return Material.REDSTONE_TORCH; + } else if (mat == Material.WALL_TORCH) { + return Material.TORCH; + } else if (mat == Material.ZOMBIE_WALL_HEAD) { + return Material.ZOMBIE_HEAD; + } else if (mat == Material.CREEPER_WALL_HEAD) { + return Material.CREEPER_HEAD; + } else if (mat == Material.PLAYER_WALL_HEAD) { + return Material.PLAYER_HEAD; + } else if (mat == Material.DRAGON_WALL_HEAD) { + return Material.DRAGON_HEAD; + } else if (mat == Material.BAMBOO_SAPLING) { + return Material.BAMBOO; + } else if (mat == Material.PISTON_HEAD || mat == Material.MOVING_PISTON) { + TechnicalPiston tp = (TechnicalPiston) b; + if (tp.getType() == TechnicalPiston.Type.NORMAL) { + return Material.PISTON; + } else { + return Material.STICKY_PISTON; + } + } + return mat; + } + + /** + * Check if a block can be + * + * @param b - block + * @param add - true to add a block, false to remove + * @return limit amount if over limit, or -1 if no limitation + */ + private int process(Block b, boolean add) { + if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) { + return -1; + } + // Check if on island + return addon.getIslands().getIslandAt(b.getLocation()).map(i -> { + String id = i.getUniqueId(); + String gameMode = addon.getGameModeName(b.getWorld()); + if (gameMode.isEmpty()) { + // Invalid world + return -1; + } + // Ignore the center block - usually bedrock, but for AOneBlock it's the magic block + if (addon.getConfig().getBoolean("ignore-center-block", true) && i.getCenter().equals(b.getLocation())) { + return -1; + } + islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode)); + if (add) { + // Check limit + int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id); + if (limit > -1) { + return limit; + } + islandCountMap.get(id).add(fixMaterial(b.getBlockData())); + } else { + if (islandCountMap.containsKey(id)) { + islandCountMap.get(id).remove(fixMaterial(b.getBlockData())); + } + } + updateSaveMap(id); + return -1; + }).orElse(-1); + } + + /** + * Removed a block from any island limit count + * @param b - block to remove + */ + public void removeBlock(Block b) { + // Get island + addon.getIslands().getIslandAt(b.getLocation()).ifPresent(i -> { + String id = i.getUniqueId(); + String gameMode = addon.getGameModeName(b.getWorld()); + if (gameMode.isEmpty()) { + // Invalid world + return; + } + islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(fixMaterial(b.getBlockData())); + updateSaveMap(id); + }); + } + private void updateSaveMap(String id) { + saveMap.putIfAbsent(id, 0); + if (saveMap.merge(id, 1, Integer::sum) > CHANGE_LIMIT) { + handler.saveObjectAsync(islandCountMap.get(id)); + saveMap.remove(id); + } + } + + + /** + * Check if this material is at its limit for world on this island + * + * @param w - world + * @param m - material + * @param id - island id + * @return limit amount if at limit or -1 if no limit + */ + private int checkLimit(World w, Material m, String id) { + // Check island limits + IslandBlockCount ibc = islandCountMap.get(id); + if (ibc.isBlockLimited(m)) { + return ibc.isAtLimit(m) ? ibc.getBlockLimit(m) + ibc.getBlockLimitOffset(m) : -1; + } + // Check specific world limits + if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) { + // Material is overridden in world + return ibc.isAtLimit(m, worldLimitMap.get(w).get(m)) ? worldLimitMap.get(w).get(m) + ibc.getBlockLimitOffset(m) : -1; + } + // Check default limit map + if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) { + return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m); + } + // No limit + return -1; + } + + /** + * Gets an aggregate map of the limits for this island + * + * @param w - world + * @param id - island id + * @return map of limits for materials + */ + public Map getMaterialLimits(World w, String id) { + // Merge limits + Map result = new EnumMap<>(Material.class); + // Default + result.putAll(defaultLimitMap); + // World + if (worldLimitMap.containsKey(w)) { + result.putAll(worldLimitMap.get(w)); + } + // Island + if (islandCountMap.containsKey(id)) { + IslandBlockCount islandBlockCount = islandCountMap.get(id); + result.putAll(islandBlockCount.getBlockLimits()); + + // Add offsets to the every limit. + islandBlockCount.getBlockLimitsOffset().forEach((material, offset) -> + result.put(material, result.getOrDefault(material, 0) + offset)); + } + return result; + } + + /** + * Removes island from the database + * + * @param e - island delete event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onIslandDelete(IslandDeleteEvent e) { + islandCountMap.remove(e.getIsland().getUniqueId()); + saveMap.remove(e.getIsland().getUniqueId()); + if (handler.objectExists(e.getIsland().getUniqueId())) { + handler.deleteID(e.getIsland().getUniqueId()); + } + } + + /** + * Set the island block count values + * + * @param islandId - island unique id + * @param ibc - island block count + */ + public void setIsland(String islandId, IslandBlockCount ibc) { + islandCountMap.put(islandId, ibc); + handler.saveObjectAsync(ibc); + } + + /** + * Get the island block count + * + * @param islandId - island unique id + * @return island block count or null if there is none yet + */ + @Nullable + public IslandBlockCount getIsland(String islandId) { + return islandCountMap.get(islandId); + } + + /** + * Get the island block count for island and make one if it does not exist + * @param island island + * @return island block count + */ + @NonNull + public IslandBlockCount getIsland(Island island) { + return islandCountMap.computeIfAbsent(island.getUniqueId(), k -> new IslandBlockCount(k, island.getGameMode())); + } + +} diff --git a/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java b/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java index 31b21a1..62d058a 100644 --- a/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java +++ b/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java @@ -1,474 +1,463 @@ -package world.bentobox.limits.listeners; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Breedable; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Villager; -import org.bukkit.event.Cancellable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; -import org.bukkit.event.entity.EntityBreedEvent; -import org.bukkit.event.hanging.HangingPlaceEvent; -import org.bukkit.event.vehicle.VehicleCreateEvent; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.limits.EntityGroup; -import world.bentobox.limits.Limits; -import world.bentobox.limits.Settings; -import world.bentobox.limits.objects.IslandBlockCount; - -public class EntityLimitListener implements Listener { - private static final String MOD_BYPASS = "mod.bypass"; - private final Limits addon; - private final List justSpawned = new ArrayList<>(); - private static final List CARDINALS = List.of(BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN); - - /** - * Handles entity and natural limitations - * @param addon - Limits object - */ - public EntityLimitListener(Limits addon) { - this.addon = addon; - } - - /** - * Handles minecart placing - * @param e - event - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onMinecart(VehicleCreateEvent e) { - // Return if not in a known world - if (!addon.inGameModeWorld(e.getVehicle().getWorld())) { - return; - } - // Debounce - if (justSpawned.contains(e.getVehicle().getUniqueId())) { - justSpawned.remove(e.getVehicle().getUniqueId()); - return; - } - // Check island - addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation()) - // Ignore spawn - .filter(i -> !i.isSpawn()) - .ifPresent(island -> { - // Check if the player is at the limit - AtLimitResult res = atLimit(island, e.getVehicle()); - if (res.hit()) { - e.setCancelled(true); - this.tellPlayers(e.getVehicle().getLocation(), e.getVehicle(), SpawnReason.MOUNT, res); - } - }); - } - - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onBreed(final EntityBreedEvent e) { - if (addon.inGameModeWorld(e.getEntity().getWorld()) - && e.getBreeder() != null - && (e.getBreeder() instanceof Player p) - && !(p.isOp() || p.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS)) - && !checkLimit(e, e.getEntity(), SpawnReason.BREEDING, false) - && e.getFather() instanceof Breedable f && e.getMother() instanceof Breedable m) { - f.setBreed(false); - m.setBreed(false); - } - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onCreatureSpawn(final CreatureSpawnEvent e) { - // Return if not in a known world - if (!addon.inGameModeWorld(e.getLocation().getWorld())) { - return; - } - if (justSpawned.contains(e.getEntity().getUniqueId())) { - justSpawned.remove(e.getEntity().getUniqueId()); - return; - } - if (e.getSpawnReason().equals(SpawnReason.SHOULDER_ENTITY) || (!(e.getEntity() instanceof Villager ) && e.getSpawnReason().equals(SpawnReason.BREEDING))) { - // Special case - do nothing - jumping around spawns parrots as they drop off player's shoulder - // Ignore breeding because it's handled in the EntityBreedEvent listener - return; - } - // Some checks can be done async, some not - if (e.getSpawnReason().equals(SpawnReason.BUILD_SNOWMAN) || e.getSpawnReason().equals(SpawnReason.BUILD_IRONGOLEM)) { - checkLimit(e, e.getEntity(), e.getSpawnReason(), addon.getSettings().isAsyncGolums()); - } else { - // Check limit sync - checkLimit(e, e.getEntity(), e.getSpawnReason(), false); - } - - } - - /** - * handles paintings and item frames - * @param e - event - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlock(HangingPlaceEvent e) { - if (!addon.inGameModeWorld(e.getBlock().getWorld())) { - return; - } - Player player = e.getPlayer(); - if (player == null) return; - addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> { - boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS); - // Check if entity can be hung - AtLimitResult res; - if (!bypass && !island.isSpawn() && (res = atLimit(island, e.getEntity())).hit()) { - // Not allowed - e.setCancelled(true); - if (res.getTypelimit() != null) { - User.getInstance(player).notify("block-limits.hit-limit", "[material]", - Util.prettifyText(e.getEntity().getType().toString()), - TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue())); - } else { - User.getInstance(player).notify("block-limits.hit-limit", "[material]", - res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")", - TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue())); - } - } - }); - } - - /** - * Check if a creature is allowed to spawn or not - * @param e - CreatureSpawnEvent - * @param async - true if check can be done async, false if not - * @return true if allowed or asycn, false if not. - */ - private boolean checkLimit(Cancellable c, LivingEntity e, SpawnReason reason, boolean async) { - Location l = e.getLocation(); - if (async) { - c.setCancelled(true); - } - return processIsland(c, e, l, reason, async); - } - - private boolean processIsland(Cancellable c, LivingEntity e, Location l, SpawnReason reason, boolean async) { - if (addon.getIslands().getIslandAt(e.getLocation()).isEmpty()) { - c.setCancelled(false); - return true; - } - Island island = addon.getIslands().getIslandAt(e.getLocation()).get(); - // Check if creature is allowed to spawn or not - AtLimitResult res = atLimit(island, e); - if (island.isSpawn() || !res.hit()) { - // Allowed - if (async) { - Bukkit.getScheduler().runTask(BentoBox.getInstance(), () -> preSpawn(e.getType(), reason, l)); - } // else do nothing - } else { - if (async) { - e.remove(); - } else { - c.setCancelled(true); - } - // If the reason is anything but because of a spawner then tell players within range - tellPlayers(l, e, reason, res); - return false; - } - return true; - } - - private void preSpawn(EntityType entityType, SpawnReason reason, Location l) { - - // Check for entities that need cleanup - switch (reason) { - case BUILD_IRONGOLEM -> detectIronGolem(l); - case BUILD_SNOWMAN -> detectSnowman(l); - case BUILD_WITHER -> { - detectWither(l); - } - default -> throw new IllegalArgumentException("Unexpected value: " + reason); - } - Entity entity = l.getWorld().spawnEntity(l, entityType); - justSpawned.add(entity.getUniqueId()); - if (reason == SpawnReason.BUILD_WITHER) { - // Create explosion - l.getWorld().createExplosion(l, 7F, true, true, entity); - } - } - - private void detectIronGolem(Location l) { - Block legs = l.getBlock(); - // Erase legs - addon.getBlockLimitListener().removeBlock(legs); - legs.setType(Material.AIR); - // Look around for possible constructions - for (BlockFace bf : CARDINALS) { - Block body = legs.getRelative(bf); - if (body.getType().equals(Material.IRON_BLOCK)) { - // Check for head - Block head = body.getRelative(bf); - if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) { - // Check for arms the rule is that they must be opposite and have nothing "beneath" them - for (BlockFace bf2 : CARDINALS) { - Block arm1 = body.getRelative(bf2); - Block arm2 = body.getRelative(bf2.getOppositeFace()); - if (arm1.getType() == Material.IRON_BLOCK && arm2.getType() == Material.IRON_BLOCK - && arm1.getRelative(bf.getOppositeFace()).isEmpty() - && arm2.getRelative(bf.getOppositeFace()).isEmpty()) { - // Erase! - addon.getBlockLimitListener().removeBlock(body); - addon.getBlockLimitListener().removeBlock(arm1); - addon.getBlockLimitListener().removeBlock(arm2); - addon.getBlockLimitListener().removeBlock(head); - body.setType(Material.AIR); - arm1.setType(Material.AIR); - arm2.setType(Material.AIR); - head.setType(Material.AIR); - return; - } - } - } - } - } - } - - private void detectSnowman(Location l) { - Block legs = l.getBlock(); - // Erase legs - addon.getBlockLimitListener().removeBlock(legs); - legs.setType(Material.AIR); - // Look around for possible constructions - for (BlockFace bf : CARDINALS) { - Block body = legs.getRelative(bf); - if (body.getType().equals(Material.SNOW_BLOCK)) { - // Check for head - Block head = body.getRelative(bf); - if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) { - // Erase - addon.getBlockLimitListener().removeBlock(body); - addon.getBlockLimitListener().removeBlock(head); - - body.setType(Material.AIR); - head.setType(Material.AIR); - return; - } - } - } - - } - - private void detectWither(Location l) { - Block legs = l.getBlock(); - // Erase legs - addon.getBlockLimitListener().removeBlock(legs); - legs.setType(Material.AIR); - // Look around for possible constructions - for (BlockFace bf : CARDINALS) { - Block body = legs.getRelative(bf); - if (isWither(body)) { - // Check for head - Block head = body.getRelative(bf); - if (head.getType().equals(Material.WITHER_SKELETON_SKULL) || head.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) { - // Check for arms the rule is that they must be opposite and have nothing "beneath" them - for (BlockFace bf2 : CARDINALS) { - Block arm1 = body.getRelative(bf2); - Block arm2 = body.getRelative(bf2.getOppositeFace()); - Block head2 = arm1.getRelative(bf); - Block head3 = arm2.getRelative(bf); - if (isWither(arm1) - && isWither(arm2) - && arm1.getRelative(bf.getOppositeFace()).isEmpty() - && arm2.getRelative(bf.getOppositeFace()).isEmpty() - && (head2.getType().equals(Material.WITHER_SKELETON_SKULL) || head2.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) - && (head3.getType().equals(Material.WITHER_SKELETON_SKULL) || head3.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) - ) { - // Erase! - addon.getBlockLimitListener().removeBlock(body); - addon.getBlockLimitListener().removeBlock(arm1); - addon.getBlockLimitListener().removeBlock(arm2); - addon.getBlockLimitListener().removeBlock(head); - addon.getBlockLimitListener().removeBlock(head2); - addon.getBlockLimitListener().removeBlock(head3); - body.setType(Material.AIR); - arm1.setType(Material.AIR); - arm2.setType(Material.AIR); - head.setType(Material.AIR); - head2.setType(Material.AIR); - head3.setType(Material.AIR); - return; - } - } - } - } - } - } - - - private boolean isWither(Block body) { - if (Util.getMinecraftVersion() < 16) { - return body.getType().equals(Material.SOUL_SAND); - } - return Tag.WITHER_SUMMON_BASE_BLOCKS.isTagged(body.getType()); - } - - /** - * Tell players within a 5 x 5 x 5 radius that the spawning was denied. Informing happens 1 tick after event - * @param l location - * @param entity entity spawned - * @param reason reason - some reasons are not reported - * @param res at limit result - */ - private void tellPlayers(Location l, Entity entity, SpawnReason reason, AtLimitResult res) { - if (reason.equals(SpawnReason.SPAWNER) || reason.equals(SpawnReason.NATURAL) - || reason.equals(SpawnReason.INFECTION) || reason.equals(SpawnReason.NETHER_PORTAL) - || reason.equals(SpawnReason.REINFORCEMENTS) || reason.equals(SpawnReason.SLIME_SPLIT)) { - return; - } - World w = l.getWorld(); - if (w == null) return; - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> { - for (Entity ent : w.getNearbyEntities(l, 5, 5, 5)) { - if (ent instanceof Player p) { - p.updateInventory(); - if (res.getTypelimit() != null) { - User.getInstance(p).notify("entity-limits.hit-limit", "[entity]", - Util.prettifyText(entity.getType().toString()), - TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue())); - } else { - User.getInstance(p).notify("entity-limits.hit-limit", "[entity]", - res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")", - TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue())); - } - } - } - }); - - } - - /** - * Checks if new entities can be added to island - * @param island - island - * @param ent - the entity - * @return true if at the limit, false if not - */ - AtLimitResult atLimit(Island island, Entity ent) { - // Check island settings first - int limitAmount = -1; - Map groupsLimits = new HashMap<>(); - - @Nullable - IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); - if (ibc != null) { - // Get the limit amount for this type - limitAmount = ibc.getEntityLimit(ent.getType()); - // Handle entity groups - List groupdefs = addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), - new ArrayList<>()); - groupdefs.forEach(def -> { - int limit = ibc.getEntityGroupLimit(def.getName()); - if (limit >= 0) - groupsLimits.put(def, limit); - }); - } - // If no island settings then try global settings - if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) { - limitAmount = addon.getSettings().getLimits().get(ent.getType()); - } - // Group limits - if (addon.getSettings().getGroupLimits().containsKey(ent.getType())) { - addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), new ArrayList<>()).stream() - .filter(group -> !groupsLimits.containsKey(group) || groupsLimits.get(group) > group.getLimit()) - .forEach(group -> groupsLimits.put(group, group.getLimit())); - } - if (limitAmount < 0 && groupsLimits.isEmpty()) { - return new AtLimitResult(); - } - - // We have to count the entities - if (limitAmount >= 0) - { - int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream() - .filter(e -> e.getType().equals(ent.getType())) - .count(); - int max = limitAmount + (ibc == null ? 0 : ibc.getEntityLimitOffset(ent.getType())); - if (count >= max) { - return new AtLimitResult(ent.getType(), max); - } - } - // Group limits - if (ibc != null) { - Map groupbyname = groupsLimits.keySet().stream() - .collect(Collectors.toMap(EntityGroup::getName, e -> e)); - ibc.getEntityGroupLimits().entrySet().stream() - .filter(e -> groupbyname.containsKey(e.getKey())) - .forEach(e -> groupsLimits.put(groupbyname.get(e.getKey()), e.getValue())); - } - // Now do the group limits - for (Map.Entry group : groupsLimits.entrySet()) { //do not use lambda - if (group.getValue() < 0) - continue; - // int count = (int) ent.getWorld().getEntities().stream() - // .filter(e -> group.getKey().contains(e.getType())) - // .filter(e -> island.inIslandSpace(e.getLocation())).count(); - int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream() - .filter(e -> group.getKey().contains(e.getType())) - .count(); - int max = group.getValue() + + (ibc == null ? 0 : ibc.getEntityGroupLimitOffset(group.getKey().getName())); - if (count >= max) { - return new AtLimitResult(group.getKey(), max); - } - } - return new AtLimitResult(); - } - - static class AtLimitResult { - private Map.Entry typelimit; - private Map.Entry grouplimit; - - public AtLimitResult() {} - - public AtLimitResult(EntityType type, int limit) { - typelimit = new AbstractMap.SimpleEntry<>(type, limit); - } - - public AtLimitResult(EntityGroup type, int limit) { - grouplimit = new AbstractMap.SimpleEntry<>(type, limit); - } - - /** - * @return true if at limit - */ - public boolean hit() { - return typelimit != null || grouplimit != null; - } - - public Map.Entry getTypelimit() { - return typelimit; - } - - public Map.Entry getGrouplimit() { - return grouplimit; - } - } -} - - +package world.bentobox.limits.listeners; + +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.*; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityBreedEvent; +import org.bukkit.event.hanging.HangingPlaceEvent; +import org.bukkit.event.vehicle.VehicleCreateEvent; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.Limits; +import world.bentobox.limits.objects.IslandBlockCount; + +import java.util.*; +import java.util.stream.Collectors; + +public class EntityLimitListener implements Listener { + private static final String MOD_BYPASS = "mod.bypass"; + private final Limits addon; + private final List justSpawned = new ArrayList<>(); + private static final List CARDINALS = List.of(BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN); + + /** + * Handles entity and natural limitations + * + * @param addon - Limits object + */ + public EntityLimitListener(Limits addon) { + this.addon = addon; + } + + /** + * Handles minecart placing + * + * @param e - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onMinecart(VehicleCreateEvent e) { + // Return if not in a known world + if (!addon.inGameModeWorld(e.getVehicle().getWorld())) { + return; + } + // Debounce + if (justSpawned.contains(e.getVehicle().getUniqueId())) { + justSpawned.remove(e.getVehicle().getUniqueId()); + return; + } + // Check island + addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation()) + // Ignore spawn + .filter(i -> !i.isSpawn()) + .ifPresent(island -> { + // Check if the player is at the limit + AtLimitResult res = atLimit(island, e.getVehicle()); + if (res.hit()) { + e.setCancelled(true); + this.tellPlayers(e.getVehicle().getLocation(), e.getVehicle(), SpawnReason.MOUNT, res); + } + }); + } + + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onBreed(final EntityBreedEvent e) { + if (addon.inGameModeWorld(e.getEntity().getWorld()) + && e.getBreeder() != null + && (e.getBreeder() instanceof Player p) + && !(p.isOp() || p.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS)) + && !checkLimit(e, e.getEntity(), SpawnReason.BREEDING, false) + && e.getFather() instanceof Breedable f && e.getMother() instanceof Breedable m) { + f.setBreed(false); + m.setBreed(false); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCreatureSpawn(final CreatureSpawnEvent e) { + // Return if not in a known world + if (!addon.inGameModeWorld(e.getLocation().getWorld())) { + return; + } + if (justSpawned.contains(e.getEntity().getUniqueId())) { + justSpawned.remove(e.getEntity().getUniqueId()); + return; + } + if (e.getSpawnReason().equals(SpawnReason.SHOULDER_ENTITY) || (!(e.getEntity() instanceof Villager) && e.getSpawnReason().equals(SpawnReason.BREEDING))) { + // Special case - do nothing - jumping around spawns parrots as they drop off player's shoulder + // Ignore breeding because it's handled in the EntityBreedEvent listener + return; + } + // Some checks can be done async, some not + if (e.getSpawnReason().equals(SpawnReason.BUILD_SNOWMAN) || e.getSpawnReason().equals(SpawnReason.BUILD_IRONGOLEM)) { + checkLimit(e, e.getEntity(), e.getSpawnReason(), addon.getSettings().isAsyncGolums()); + } else { + // Check limit sync + checkLimit(e, e.getEntity(), e.getSpawnReason(), false); + } + + } + + /** + * handles paintings and item frames + * + * @param e - event + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlock(HangingPlaceEvent e) { + if (!addon.inGameModeWorld(e.getBlock().getWorld())) { + return; + } + Player player = e.getPlayer(); + if (player == null) return; + addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> { + boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS); + // Check if entity can be hung + AtLimitResult res; + if (!bypass && !island.isSpawn() && (res = atLimit(island, e.getEntity())).hit()) { + // Not allowed + e.setCancelled(true); + if (res.getTypelimit() != null) { + User.getInstance(player).notify("block-limits.hit-limit", "[material]", + Util.prettifyText(e.getEntity().getType().toString()), + TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue())); + } else { + User.getInstance(player).notify("block-limits.hit-limit", "[material]", + res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")", + TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue())); + } + } + }); + } + + /** + * Check if a creature is allowed to spawn or not + * + * @param e - CreatureSpawnEvent + * @param async - true if check can be done async, false if not + * @return true if allowed or asycn, false if not. + */ + private boolean checkLimit(Cancellable c, LivingEntity e, SpawnReason reason, boolean async) { + Location l = e.getLocation(); + if (async) { + c.setCancelled(true); + } + return processIsland(c, e, l, reason, async); + } + + private boolean processIsland(Cancellable c, LivingEntity e, Location l, SpawnReason reason, boolean async) { + if (addon.getIslands().getIslandAt(e.getLocation()).isEmpty()) { + c.setCancelled(false); + return true; + } + Island island = addon.getIslands().getIslandAt(e.getLocation()).get(); + // Check if creature is allowed to spawn or not + AtLimitResult res = atLimit(island, e); + if (island.isSpawn() || !res.hit()) { + // Allowed + if (async) { + Bukkit.getScheduler().runTask(BentoBox.getInstance(), () -> preSpawn(e.getType(), reason, l)); + } // else do nothing + } else { + if (async) { + e.remove(); + } else { + c.setCancelled(true); + } + // If the reason is anything but because of a spawner then tell players within range + tellPlayers(l, e, reason, res); + return false; + } + return true; + } + + private void preSpawn(EntityType entityType, SpawnReason reason, Location l) { + + // Check for entities that need cleanup + switch (reason) { + case BUILD_IRONGOLEM -> detectIronGolem(l); + case BUILD_SNOWMAN -> detectSnowman(l); + case BUILD_WITHER -> { + detectWither(l); + } + default -> throw new IllegalArgumentException("Unexpected value: " + reason); + } + Entity entity = l.getWorld().spawnEntity(l, entityType); + justSpawned.add(entity.getUniqueId()); + if (reason == SpawnReason.BUILD_WITHER) { + // Create explosion + l.getWorld().createExplosion(l, 7F, true, true, entity); + } + } + + private void detectIronGolem(Location l) { + Block legs = l.getBlock(); + // Erase legs + addon.getBlockLimitListener().removeBlock(legs); + legs.setType(Material.AIR); + // Look around for possible constructions + for (BlockFace bf : CARDINALS) { + Block body = legs.getRelative(bf); + if (body.getType().equals(Material.IRON_BLOCK)) { + // Check for head + Block head = body.getRelative(bf); + if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) { + // Check for arms the rule is that they must be opposite and have nothing "beneath" them + for (BlockFace bf2 : CARDINALS) { + Block arm1 = body.getRelative(bf2); + Block arm2 = body.getRelative(bf2.getOppositeFace()); + if (arm1.getType() == Material.IRON_BLOCK && arm2.getType() == Material.IRON_BLOCK + && arm1.getRelative(bf.getOppositeFace()).isEmpty() + && arm2.getRelative(bf.getOppositeFace()).isEmpty()) { + // Erase! + addon.getBlockLimitListener().removeBlock(body); + addon.getBlockLimitListener().removeBlock(arm1); + addon.getBlockLimitListener().removeBlock(arm2); + addon.getBlockLimitListener().removeBlock(head); + body.setType(Material.AIR); + arm1.setType(Material.AIR); + arm2.setType(Material.AIR); + head.setType(Material.AIR); + return; + } + } + } + } + } + } + + private void detectSnowman(Location l) { + Block legs = l.getBlock(); + // Erase legs + addon.getBlockLimitListener().removeBlock(legs); + legs.setType(Material.AIR); + // Look around for possible constructions + for (BlockFace bf : CARDINALS) { + Block body = legs.getRelative(bf); + if (body.getType().equals(Material.SNOW_BLOCK)) { + // Check for head + Block head = body.getRelative(bf); + if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) { + // Erase + addon.getBlockLimitListener().removeBlock(body); + addon.getBlockLimitListener().removeBlock(head); + + body.setType(Material.AIR); + head.setType(Material.AIR); + return; + } + } + } + + } + + private void detectWither(Location l) { + Block legs = l.getBlock(); + // Erase legs + addon.getBlockLimitListener().removeBlock(legs); + legs.setType(Material.AIR); + // Look around for possible constructions + for (BlockFace bf : CARDINALS) { + Block body = legs.getRelative(bf); + if (isWither(body)) { + // Check for head + Block head = body.getRelative(bf); + if (head.getType().equals(Material.WITHER_SKELETON_SKULL) || head.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) { + // Check for arms the rule is that they must be opposite and have nothing "beneath" them + for (BlockFace bf2 : CARDINALS) { + Block arm1 = body.getRelative(bf2); + Block arm2 = body.getRelative(bf2.getOppositeFace()); + Block head2 = arm1.getRelative(bf); + Block head3 = arm2.getRelative(bf); + if (isWither(arm1) + && isWither(arm2) + && arm1.getRelative(bf.getOppositeFace()).isEmpty() + && arm2.getRelative(bf.getOppositeFace()).isEmpty() + && (head2.getType().equals(Material.WITHER_SKELETON_SKULL) || head2.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) + && (head3.getType().equals(Material.WITHER_SKELETON_SKULL) || head3.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) + ) { + // Erase! + addon.getBlockLimitListener().removeBlock(body); + addon.getBlockLimitListener().removeBlock(arm1); + addon.getBlockLimitListener().removeBlock(arm2); + addon.getBlockLimitListener().removeBlock(head); + addon.getBlockLimitListener().removeBlock(head2); + addon.getBlockLimitListener().removeBlock(head3); + body.setType(Material.AIR); + arm1.setType(Material.AIR); + arm2.setType(Material.AIR); + head.setType(Material.AIR); + head2.setType(Material.AIR); + head3.setType(Material.AIR); + return; + } + } + } + } + } + } + + + private boolean isWither(Block body) { + if (Util.getMinecraftVersion() < 16) { + return body.getType().equals(Material.SOUL_SAND); + } + return Tag.WITHER_SUMMON_BASE_BLOCKS.isTagged(body.getType()); + } + + /** + * Tell players within a 5 x 5 x 5 radius that the spawning was denied. Informing happens 1 tick after event + * + * @param l location + * @param entity entity spawned + * @param reason reason - some reasons are not reported + * @param res at limit result + */ + private void tellPlayers(Location l, Entity entity, SpawnReason reason, AtLimitResult res) { + if (reason.equals(SpawnReason.SPAWNER) || reason.equals(SpawnReason.NATURAL) + || reason.equals(SpawnReason.INFECTION) || reason.equals(SpawnReason.NETHER_PORTAL) + || reason.equals(SpawnReason.REINFORCEMENTS) || reason.equals(SpawnReason.SLIME_SPLIT)) { + return; + } + World w = l.getWorld(); + if (w == null) return; + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> { + for (Entity ent : w.getNearbyEntities(l, 5, 5, 5)) { + if (ent instanceof Player p) { + p.updateInventory(); + if (res.getTypelimit() != null) { + User.getInstance(p).notify("entity-limits.hit-limit", "[entity]", + Util.prettifyText(entity.getType().toString()), + TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue())); + } else { + User.getInstance(p).notify("entity-limits.hit-limit", "[entity]", + res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")", + TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue())); + } + } + } + }); + + } + + /** + * Checks if new entities can be added to island + * + * @param island - island + * @param ent - the entity + * @return true if at the limit, false if not + */ + AtLimitResult atLimit(Island island, Entity ent) { + // Check island settings first + int limitAmount = -1; + Map groupsLimits = new HashMap<>(); + + @Nullable + IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); + if (ibc != null) { + // Get the limit amount for this type + limitAmount = ibc.getEntityLimit(ent.getType()); + // Handle entity groups + List groupdefs = addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), + new ArrayList<>()); + groupdefs.forEach(def -> { + int limit = ibc.getEntityGroupLimit(def.getName()); + if (limit >= 0) + groupsLimits.put(def, limit); + }); + } + // If no island settings then try global settings + if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) { + limitAmount = addon.getSettings().getLimits().get(ent.getType()); + } + // Group limits + if (addon.getSettings().getGroupLimits().containsKey(ent.getType())) { + addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), new ArrayList<>()).stream() + .filter(group -> !groupsLimits.containsKey(group) || groupsLimits.get(group) > group.getLimit()) + .forEach(group -> groupsLimits.put(group, group.getLimit())); + } + if (limitAmount < 0 && groupsLimits.isEmpty()) { + return new AtLimitResult(); + } + + // We have to count the entities + if (limitAmount >= 0) { + int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream() + .filter(e -> e.getType().equals(ent.getType())) + .count(); + int max = limitAmount + (ibc == null ? 0 : ibc.getEntityLimitOffset(ent.getType())); + if (count >= max) { + return new AtLimitResult(ent.getType(), max); + } + } + // Group limits + if (ibc != null) { + Map groupbyname = groupsLimits.keySet().stream() + .collect(Collectors.toMap(EntityGroup::getName, e -> e)); + ibc.getEntityGroupLimits().entrySet().stream() + .filter(e -> groupbyname.containsKey(e.getKey())) + .forEach(e -> groupsLimits.put(groupbyname.get(e.getKey()), e.getValue())); + } + // Now do the group limits + for (Map.Entry group : groupsLimits.entrySet()) { //do not use lambda + if (group.getValue() < 0) + continue; + // int count = (int) ent.getWorld().getEntities().stream() + // .filter(e -> group.getKey().contains(e.getType())) + // .filter(e -> island.inIslandSpace(e.getLocation())).count(); + int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream() + .filter(e -> group.getKey().contains(e.getType())) + .count(); + int max = group.getValue() + +(ibc == null ? 0 : ibc.getEntityGroupLimitOffset(group.getKey().getName())); + if (count >= max) { + return new AtLimitResult(group.getKey(), max); + } + } + return new AtLimitResult(); + } + + static class AtLimitResult { + private Map.Entry typelimit; + private Map.Entry grouplimit; + + public AtLimitResult() { + } + + public AtLimitResult(EntityType type, int limit) { + typelimit = new AbstractMap.SimpleEntry<>(type, limit); + } + + public AtLimitResult(EntityGroup type, int limit) { + grouplimit = new AbstractMap.SimpleEntry<>(type, limit); + } + + /** + * @return true if at limit + */ + public boolean hit() { + return typelimit != null || grouplimit != null; + } + + public Map.Entry getTypelimit() { + return typelimit; + } + + public Map.Entry getGrouplimit() { + return grouplimit; + } + } +} + + diff --git a/src/main/java/world/bentobox/limits/listeners/JoinListener.java b/src/main/java/world/bentobox/limits/listeners/JoinListener.java index 3bc93ae..b163cbd 100644 --- a/src/main/java/world/bentobox/limits/listeners/JoinListener.java +++ b/src/main/java/world/bentobox/limits/listeners/JoinListener.java @@ -1,277 +1,277 @@ -package world.bentobox.limits.listeners; - -import java.util.Arrays; -import java.util.Locale; -import java.util.Objects; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.eclipse.jdt.annotation.NonNull; - -import world.bentobox.bentobox.api.events.island.IslandEvent; -import world.bentobox.bentobox.api.events.island.IslandEvent.Reason; -import world.bentobox.bentobox.api.events.team.TeamSetownerEvent; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.EntityGroup; -import world.bentobox.limits.Limits; -import world.bentobox.limits.events.LimitsJoinPermCheckEvent; -import world.bentobox.limits.events.LimitsPermCheckEvent; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * Sets block limits based on player permission - * - * @author tastybento - * - */ -public class JoinListener implements Listener { - - private final Limits addon; - - public JoinListener(Limits addon) { - this.addon = addon; - } - - /** - * Check and set the permissions of the player and how they affect the island - * limits - * - * @param player - player - * @param permissionPrefix - permission prefix for this game mode - * @param islandId - island string id - * @param gameMode - game mode string doing the checking - */ - public void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) { - IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId); - // Check permissions - if (ibc != null) { - // Clear permission limits - ibc.getEntityLimits().clear(); - ibc.getEntityGroupLimits().clear(); - ibc.getBlockLimits().clear(); - } - for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { - if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix) - || badSyntaxCheck(perms, player.getName(), permissionPrefix)) { - continue; - } - // Check formatting - String[] split = perms.getPermission().split("\\."); - // Entities & materials - EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3])) - .findFirst().orElse(null); - Material m = Arrays.stream(Material.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst() - .orElse(null); - EntityGroup entgroup = addon.getSettings().getGroupLimitDefinitions().stream() - .filter(t -> t.getName().equalsIgnoreCase(split[3])).findFirst().orElse(null); - - if (entgroup == null && et == null && m == null) { - logError(player.getName(), perms.getPermission(), - split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material or entity type/group."); - break; - } - // Make an ibc if required - if (ibc == null) { - ibc = new IslandBlockCount(islandId, gameMode); - } - // Get the value - int value = Integer.parseInt(split[4]); - addon.log("Setting login limit via perm for " + player.getName() + "..."); - - // Fire perm check event - LimitsPermCheckEvent l = new LimitsPermCheckEvent(player, islandId, ibc, entgroup, et, m, value); - Bukkit.getPluginManager().callEvent(l); - if (l.isCancelled()) { - addon.log("Permissions not set because another addon/plugin canceled setting."); - continue; - } - // Use event values - ibc = l.getIbc(); - // Make an ibc if required - if (ibc == null) { - ibc = new IslandBlockCount(islandId, gameMode); - } - // Run null checks and set ibc - runNullCheckAndSet(ibc, l); - } - // Check removed permissions - // If any changes have been made then store it - don't make files unless they - // are needed - if (ibc != null) - addon.getBlockLimitListener().setIsland(islandId, ibc); - } - - private boolean badSyntaxCheck(PermissionAttachmentInfo perms, String name, String permissionPrefix) { - // No wildcards - if (perms.getPermission().contains(permissionPrefix + "*")) { - logError(name, perms.getPermission(), "wildcards are not allowed."); - return true; - } - // Check formatting - String[] split = perms.getPermission().split("\\."); - if (split.length != 5) { - logError(name, perms.getPermission(), "format must be '" + permissionPrefix + "MATERIAL.NUMBER', '" - + permissionPrefix + "ENTITY-TYPE.NUMBER', or '" + permissionPrefix + "ENTITY-GROUP.NUMBER'"); - return true; - } - // Check value - try { - Integer.parseInt(split[4]); - } catch (Exception e) { - logError(name, perms.getPermission(), "the last part MUST be an integer!"); - return true; - } - return false; - } - - private void runNullCheckAndSet(@NonNull IslandBlockCount ibc, @NonNull LimitsPermCheckEvent l) { - EntityGroup entgroup = l.getEntityGroup(); - EntityType et = l.getEntityType(); - Material m = l.getMaterial(); - int value = l.getValue(); - if (entgroup != null) { - // Entity group limit - int v = Math.max(ibc.getEntityGroupLimit(entgroup.getName()), value); - ibc.setEntityGroupLimit(entgroup.getName(), v); - addon.log("Setting group limit " + entgroup.getName() + " " + v); - } else if (et != null && m == null) { - // Entity limit - int v = Math.max(ibc.getEntityLimit(et), value); - ibc.setEntityLimit(et, v); - addon.log("Setting entity limit " + et + " " + v); - } else if (m != null && et == null) { - // Block limit - int v = Math.max(ibc.getBlockLimit(m), value); - addon.log("Setting block limit " + m + " " + v); - ibc.setBlockLimit(m, v); - } else { - if (m != null && m.isBlock()) { - int v = Math.max(ibc.getBlockLimit(m), value); - addon.log("Setting block limit " + m + " " + v); - // Material limit - ibc.setBlockLimit(m, v); - } else if (et != null) { - int v = Math.max(ibc.getEntityLimit(et), value); - addon.log("Setting entity limit " + et + " " + v); - // This is an entity setting - ibc.setEntityLimit(et, v); - } - } - - } - - private void logError(String name, String perm, String error) { - addon.logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); - } - - /* - * Event handling - */ - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onNewIsland(IslandEvent e) { - if (!e.getReason().equals(Reason.CREATED) && !e.getReason().equals(Reason.RESETTED) - && !e.getReason().equals(Reason.REGISTERED)) { - return; - } - setOwnerPerms(e.getIsland(), e.getOwner()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onOwnerChange(TeamSetownerEvent e) { - removeOwnerPerms(e.getIsland()); - setOwnerPerms(e.getIsland(), e.getNewOwner()); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onPlayerJoin(PlayerJoinEvent e) { - // Check if player has any islands in the game modes - addon.getGameModes().forEach(gm -> { - addon.getIslands().getIslands(gm.getOverWorld(), e.getPlayer().getUniqueId()).stream() - .filter(island -> e.getPlayer().getUniqueId().equals(island.getOwner())) - .map(Island::getUniqueId).forEach(islandId -> { - IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId); - if (!joinEventCheck(e.getPlayer(), islandId, ibc)) { - checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId, - gm.getDescription().getName()); - } - }); - }); - } - - /** - * Fire event so other addons can cancel this permissions change - * - * @param player player - * @param islandId island id - * @param ibc island block count - * @return true if canceled - */ - private boolean joinEventCheck(Player player, String islandId, IslandBlockCount ibc) { - // Fire event, so other addons can cancel this permissions change - LimitsJoinPermCheckEvent e = new LimitsJoinPermCheckEvent(player, islandId, ibc); - Bukkit.getPluginManager().callEvent(e); - if (e.isCancelled()) { - return true; - } - // Get ibc from event if it has changed - ibc = e.getIbc(); - // If perms should be ignored, but the IBC given in the event used, then set it - // and return - if (e.isIgnorePerms() && ibc != null) { - addon.getBlockLimitListener().setIsland(islandId, ibc); - return true; - } - return false; - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onUnregisterIsland(IslandEvent e) { - if (!e.getReason().equals(Reason.UNREGISTERED)) { - return; - } - removeOwnerPerms(e.getIsland()); - } - - /* - * Utility methods - */ - - private void removeOwnerPerms(Island island) { - World world = island.getWorld(); - if (addon.inGameModeWorld(world)) { - IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); - if (ibc != null) { - ibc.getBlockLimits().clear(); - } - } - } - - private void setOwnerPerms(Island island, UUID ownerUUID) { - World world = island.getWorld(); - if (addon.inGameModeWorld(world)) { - // Check if owner is online - OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerUUID); - if (owner.isOnline()) { - // Set perm-based limits - String prefix = addon.getGameModePermPrefix(world); - String name = addon.getGameModeName(world); - if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) { - checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.", - island.getUniqueId(), name); - } - } - } - } - -} +package world.bentobox.limits.listeners; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.eclipse.jdt.annotation.NonNull; + +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.events.island.IslandEvent.Reason; +import world.bentobox.bentobox.api.events.team.TeamSetownerEvent; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.EntityGroup; +import world.bentobox.limits.Limits; +import world.bentobox.limits.events.LimitsJoinPermCheckEvent; +import world.bentobox.limits.events.LimitsPermCheckEvent; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * Sets block limits based on player permission + * + * @author tastybento + * + */ +public class JoinListener implements Listener { + + private final Limits addon; + + public JoinListener(Limits addon) { + this.addon = addon; + } + + /** + * Check and set the permissions of the player and how they affect the island + * limits + * + * @param player - player + * @param permissionPrefix - permission prefix for this game mode + * @param islandId - island string id + * @param gameMode - game mode string doing the checking + */ + public void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) { + IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId); + // Check permissions + if (ibc != null) { + // Clear permission limits + ibc.getEntityLimits().clear(); + ibc.getEntityGroupLimits().clear(); + ibc.getBlockLimits().clear(); + } + for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { + if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix) + || badSyntaxCheck(perms, player.getName(), permissionPrefix)) { + continue; + } + // Check formatting + String[] split = perms.getPermission().split("\\."); + // Entities & materials + EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3])) + .findFirst().orElse(null); + Material m = Arrays.stream(Material.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst() + .orElse(null); + EntityGroup entgroup = addon.getSettings().getGroupLimitDefinitions().stream() + .filter(t -> t.getName().equalsIgnoreCase(split[3])).findFirst().orElse(null); + + if (entgroup == null && et == null && m == null) { + logError(player.getName(), perms.getPermission(), + split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material or entity type/group."); + break; + } + // Make an ibc if required + if (ibc == null) { + ibc = new IslandBlockCount(islandId, gameMode); + } + // Get the value + int value = Integer.parseInt(split[4]); + addon.log("Setting login limit via perm for " + player.getName() + "..."); + + // Fire perm check event + LimitsPermCheckEvent l = new LimitsPermCheckEvent(player, islandId, ibc, entgroup, et, m, value); + Bukkit.getPluginManager().callEvent(l); + if (l.isCancelled()) { + addon.log("Permissions not set because another addon/plugin canceled setting."); + continue; + } + // Use event values + ibc = l.getIbc(); + // Make an ibc if required + if (ibc == null) { + ibc = new IslandBlockCount(islandId, gameMode); + } + // Run null checks and set ibc + runNullCheckAndSet(ibc, l); + } + // Check removed permissions + // If any changes have been made then store it - don't make files unless they + // are needed + if (ibc != null) + addon.getBlockLimitListener().setIsland(islandId, ibc); + } + + private boolean badSyntaxCheck(PermissionAttachmentInfo perms, String name, String permissionPrefix) { + // No wildcards + if (perms.getPermission().contains(permissionPrefix + "*")) { + logError(name, perms.getPermission(), "wildcards are not allowed."); + return true; + } + // Check formatting + String[] split = perms.getPermission().split("\\."); + if (split.length != 5) { + logError(name, perms.getPermission(), "format must be '" + permissionPrefix + "MATERIAL.NUMBER', '" + + permissionPrefix + "ENTITY-TYPE.NUMBER', or '" + permissionPrefix + "ENTITY-GROUP.NUMBER'"); + return true; + } + // Check value + try { + Integer.parseInt(split[4]); + } catch (Exception e) { + logError(name, perms.getPermission(), "the last part MUST be an integer!"); + return true; + } + return false; + } + + private void runNullCheckAndSet(@NonNull IslandBlockCount ibc, @NonNull LimitsPermCheckEvent l) { + EntityGroup entgroup = l.getEntityGroup(); + EntityType et = l.getEntityType(); + Material m = l.getMaterial(); + int value = l.getValue(); + if (entgroup != null) { + // Entity group limit + int v = Math.max(ibc.getEntityGroupLimit(entgroup.getName()), value); + ibc.setEntityGroupLimit(entgroup.getName(), v); + addon.log("Setting group limit " + entgroup.getName() + " " + v); + } else if (et != null && m == null) { + // Entity limit + int v = Math.max(ibc.getEntityLimit(et), value); + ibc.setEntityLimit(et, v); + addon.log("Setting entity limit " + et + " " + v); + } else if (m != null && et == null) { + // Block limit + int v = Math.max(ibc.getBlockLimit(m), value); + addon.log("Setting block limit " + m + " " + v); + ibc.setBlockLimit(m, v); + } else { + if (m != null && m.isBlock()) { + int v = Math.max(ibc.getBlockLimit(m), value); + addon.log("Setting block limit " + m + " " + v); + // Material limit + ibc.setBlockLimit(m, v); + } else if (et != null) { + int v = Math.max(ibc.getEntityLimit(et), value); + addon.log("Setting entity limit " + et + " " + v); + // This is an entity setting + ibc.setEntityLimit(et, v); + } + } + + } + + private void logError(String name, String perm, String error) { + addon.logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring..."); + } + + /* + * Event handling + */ + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onNewIsland(IslandEvent e) { + if (!e.getReason().equals(Reason.CREATED) && !e.getReason().equals(Reason.RESETTED) + && !e.getReason().equals(Reason.REGISTERED)) { + return; + } + setOwnerPerms(e.getIsland(), e.getOwner()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onOwnerChange(TeamSetownerEvent e) { + removeOwnerPerms(e.getIsland()); + setOwnerPerms(e.getIsland(), e.getNewOwner()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPlayerJoin(PlayerJoinEvent e) { + // Check if player has any islands in the game modes + addon.getGameModes().forEach(gm -> { + addon.getIslands().getIslands(gm.getOverWorld(), e.getPlayer().getUniqueId()).stream() + .filter(island -> e.getPlayer().getUniqueId().equals(island.getOwner())) + .map(Island::getUniqueId).forEach(islandId -> { + IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId); + if (!joinEventCheck(e.getPlayer(), islandId, ibc)) { + checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId, + gm.getDescription().getName()); + } + }); + }); + } + + /** + * Fire event so other addons can cancel this permissions change + * + * @param player player + * @param islandId island id + * @param ibc island block count + * @return true if canceled + */ + private boolean joinEventCheck(Player player, String islandId, IslandBlockCount ibc) { + // Fire event, so other addons can cancel this permissions change + LimitsJoinPermCheckEvent e = new LimitsJoinPermCheckEvent(player, islandId, ibc); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) { + return true; + } + // Get ibc from event if it has changed + ibc = e.getIbc(); + // If perms should be ignored, but the IBC given in the event used, then set it + // and return + if (e.isIgnorePerms() && ibc != null) { + addon.getBlockLimitListener().setIsland(islandId, ibc); + return true; + } + return false; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onUnregisterIsland(IslandEvent e) { + if (!e.getReason().equals(Reason.UNREGISTERED)) { + return; + } + removeOwnerPerms(e.getIsland()); + } + + /* + * Utility methods + */ + + private void removeOwnerPerms(Island island) { + World world = island.getWorld(); + if (addon.inGameModeWorld(world)) { + IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId()); + if (ibc != null) { + ibc.getBlockLimits().clear(); + } + } + } + + private void setOwnerPerms(Island island, UUID ownerUUID) { + World world = island.getWorld(); + if (addon.inGameModeWorld(world)) { + // Check if owner is online + OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerUUID); + if (owner.isOnline()) { + // Set perm-based limits + String prefix = addon.getGameModePermPrefix(world); + String name = addon.getGameModeName(world); + if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) { + checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.", + island.getUniqueId(), name); + } + } + } + } + +} diff --git a/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java b/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java index 03caac7..b162698 100644 --- a/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java +++ b/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java @@ -1,92 +1,92 @@ -package world.bentobox.limits.objects; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import com.google.gson.annotations.Expose; - -import world.bentobox.bentobox.database.objects.DataObject; -import world.bentobox.bentobox.database.objects.Table; - -/** - * @author tastybento - * - */ -@Table(name = "EntityLimits") -public class EntityLimitsDO implements DataObject { - - @Expose - private String uniqueId = ""; - @Expose - private Map spawnLoc = new HashMap<>(); - - public EntityLimitsDO() {} - - public EntityLimitsDO(String uniqueId) { - this.uniqueId = uniqueId; - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() - */ - @Override - public String getUniqueId() { - return uniqueId; - } - - /* (non-Javadoc) - * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String) - */ - @Override - public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; - - } - - /** - * @return the spawnLoc - */ - public Map getSpawnLoc() { - return spawnLoc; - } - - /** - * @param spawnLoc the spawnLoc to set - */ - public void setSpawnLoc(Map spawnLoc) { - this.spawnLoc = spawnLoc; - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode()); - return result; - } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof EntityLimitsDO other)) { - return false; - } - if (uniqueId == null) { - return other.uniqueId == null; - } else return uniqueId.equals(other.uniqueId); - } - - -} +package world.bentobox.limits.objects; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; + +/** + * @author tastybento + * + */ +@Table(name = "EntityLimits") +public class EntityLimitsDO implements DataObject { + + @Expose + private String uniqueId = ""; + @Expose + private Map spawnLoc = new HashMap<>(); + + public EntityLimitsDO() {} + + public EntityLimitsDO(String uniqueId) { + this.uniqueId = uniqueId; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() + */ + @Override + public String getUniqueId() { + return uniqueId; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String) + */ + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + + } + + /** + * @return the spawnLoc + */ + public Map getSpawnLoc() { + return spawnLoc; + } + + /** + * @param spawnLoc the spawnLoc to set + */ + public void setSpawnLoc(Map spawnLoc) { + this.spawnLoc = spawnLoc; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof EntityLimitsDO other)) { + return false; + } + if (uniqueId == null) { + return other.uniqueId == null; + } else return uniqueId.equals(other.uniqueId); + } + + +} diff --git a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java index 1ff6cc8..46d6f9f 100644 --- a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java +++ b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java @@ -1,417 +1,417 @@ -package world.bentobox.limits.objects; - -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import org.bukkit.Material; -import org.bukkit.entity.EntityType; - -import com.google.gson.annotations.Expose; - -import world.bentobox.bentobox.database.objects.DataObject; -import world.bentobox.bentobox.database.objects.Table; - -/** - * @author tastybento - * - */ -@Table(name = "IslandBlockCount") -public class IslandBlockCount implements DataObject { - - @Expose - private Map blockCounts = new EnumMap<>(Material.class); - - /** - * Permission based limits - */ - @Expose - private Map blockLimits = new EnumMap<>(Material.class); - - @Expose - private Map blockLimitsOffset = new EnumMap<>(Material.class); - - private boolean changed; - - @Expose - private Map entityGroupLimits = new HashMap<>(); - @Expose - private Map entityGroupLimitsOffset = new HashMap<>(); - @Expose - private Map entityLimits = new EnumMap<>(EntityType.class); - @Expose - private Map entityLimitsOffset = new EnumMap<>(EntityType.class); - @Expose - private String gameMode; - @Expose - private String uniqueId; - - /** - * Create an island block count object - * - * @param islandId - unique Island ID string - * @param gameMode - Game mode name from gm.getDescription().getName() - */ - public IslandBlockCount(String islandId, String gameMode) { - this.uniqueId = islandId; - this.gameMode = gameMode; - setChanged(); - } - - /** - * Add a material to the count - * - * @param material - material - */ - public void add(Material material) { - getBlockCounts().merge(material, 1, Integer::sum); - setChanged(); - } - - /** - * Clear all island-specific entity group limits - */ - public void clearEntityGroupLimits() { - entityGroupLimits.clear(); - setChanged(); - } - - /** - * Clear all island-specific entity type limits - */ - public void clearEntityLimits() { - entityLimits.clear(); - setChanged(); - } - - /** - * Get the block count for this material for this island - * - * @param m - material - * @return count - */ - public Integer getBlockCount(Material m) { - return getBlockCounts().getOrDefault(m, 0); - } - - /** - * @return the blockCount - */ - public Map getBlockCounts() { - if (blockCounts == null) { - blockCounts = new EnumMap<>(Material.class); - } - return blockCounts; - } - - /** - * Get the block limit for this material for this island - * - * @param m - material - * @return limit or -1 for unlimited - */ - public int getBlockLimit(Material m) { - return getBlockLimits().getOrDefault(m, -1); - } - - /** - * Get the block offset for this material for this island - * - * @param m - material - * @return offset - */ - public int getBlockLimitOffset(Material m) { - return getBlockLimitsOffset().getOrDefault(m, 0); - } - - /** - * @return the blockLimits - */ - public Map getBlockLimits() { - return Objects.requireNonNullElse(blockLimits, new EnumMap<>(Material.class)); - } - - /** - * @return the blockLimitsOffset - */ - public Map getBlockLimitsOffset() { - if (blockLimitsOffset == null) { - blockLimitsOffset = new EnumMap<>(Material.class); - } - return blockLimitsOffset; - } - - /** - * Get the limit for an entity group - * - * @param name - entity group - * @return limit or -1 for unlimited - */ - public int getEntityGroupLimit(String name) { - return getEntityGroupLimits().getOrDefault(name, -1); - } - - /** - * Get the offset for an entity group - * - * @param name - entity group - * @return offset - */ - public int getEntityGroupLimitOffset(String name) { - return getEntityGroupLimitsOffset().getOrDefault(name, 0); - } - - /** - * @return the entityGroupLimits - */ - public Map getEntityGroupLimits() { - return Objects.requireNonNullElse(entityGroupLimits, new HashMap<>()); - } - - /** - * @return the entityGroupLimitsOffset - */ - public Map getEntityGroupLimitsOffset() { - if (entityGroupLimitsOffset == null) { - entityGroupLimitsOffset = new HashMap<>(); - } - return entityGroupLimitsOffset; - } - - /** - * Get the limit for an entity type - * - * @param t - entity type - * @return limit or -1 for unlimited - */ - public int getEntityLimit(EntityType t) { - return getEntityLimits().getOrDefault(t, -1); - } - - /** - * Get the limit offset for an entity type - * - * @param t - entity type - * @return offset - */ - public int getEntityLimitOffset(EntityType t) { - return getEntityLimitsOffset().getOrDefault(t, 0); - } - - /** - * @return the entityLimits - */ - public Map getEntityLimits() { - return Objects.requireNonNullElse(entityLimits, new EnumMap<>(EntityType.class)); - } - - /** - * @return the entityLimitsOffset - */ - public Map getEntityLimitsOffset() { - if (entityLimitsOffset == null) { - entityLimitsOffset = new EnumMap<>(EntityType.class); - } - return entityLimitsOffset; - } - - /** - * @return the gameMode - */ - public String getGameMode() { - return gameMode; - } - - /* - * (non-Javadoc) - * - * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() - */ - @Override - public String getUniqueId() { - return uniqueId; - } - - /** - * Check if no more of this material can be added to this island - * - * @param m - material - * @return true if no more material can be added - */ - public boolean isAtLimit(Material m) { - // Check island limits first - return getBlockLimits().containsKey(m) - && getBlockCounts().getOrDefault(m, 0) >= getBlockLimit(m) + this.getBlockLimitOffset(m); - } - - /** - * Check if this material is at or over a limit - * - * @param material - block material - * @param limit - limit to check - * @return true if count is >= limit - */ - public boolean isAtLimit(Material material, int limit) { - return getBlockCounts().getOrDefault(material, 0) >= limit + this.getBlockLimitOffset(material); - } - - public boolean isBlockLimited(Material m) { - return getBlockLimits().containsKey(m); - } - - /** - * @return the changed - */ - public boolean isChanged() { - return changed; - } - - public boolean isGameMode(String gameMode) { - return getGameMode().equals(gameMode); - } - - /** - * Remove a material from the count - * - * @param material - material - */ - public void remove(Material material) { - getBlockCounts().put(material, getBlockCounts().getOrDefault(material, 0) - 1); - getBlockCounts().values().removeIf(v -> v <= 0); - setChanged(); - } - - /** - * @param blockCounts the blockCount to set - */ - public void setBlockCounts(Map blockCounts) { - this.blockCounts = blockCounts; - setChanged(); - } - - /** - * Set the block limit for this material for this island - * - * @param m - material - * @param limit - maximum number allowed - */ - public void setBlockLimit(Material m, int limit) { - getBlockLimits().put(m, limit); - setChanged(); - } - - /** - * @param blockLimits the blockLimits to set - */ - public void setBlockLimits(Map blockLimits) { - this.blockLimits = blockLimits; - setChanged(); - } - - /** - * Set an offset to a block limit. This will increase/decrease the value of the - * limit. - * - * @param m material - * @param blockLimitsOffset the blockLimitsOffset to set - */ - public void setBlockLimitsOffset(Material m, Integer blockLimitsOffset) { - getBlockLimitsOffset().put(m, blockLimitsOffset); - } - - /** - * Mark changed - */ - public void setChanged() { - this.changed = true; - } - - /** - * @param changed the changed to set - */ - public void setChanged(boolean changed) { - this.changed = changed; - } - - /** - * Set an island-specific entity group limit - * - * @param name - entity group - * @param limit - limit - */ - public void setEntityGroupLimit(String name, int limit) { - getEntityGroupLimits().put(name, limit); - setChanged(); - } - - /** - * @param entityGroupLimits the entityGroupLimits to set - */ - public void setEntityGroupLimits(Map entityGroupLimits) { - this.entityGroupLimits = entityGroupLimits; - setChanged(); - } - - /** - * Set an offset to an entity group limit. This will increase/decrease the value - * of the limit. - * - * @param name group name - * @param entityGroupLimitsOffset the entityGroupLimitsOffset to set - */ - public void setEntityGroupLimitsOffset(String name, Integer entityGroupLimitsOffset) { - getEntityGroupLimitsOffset().put(name, entityGroupLimitsOffset); - } - - /** - * Set an island-specific entity type limit - * - * @param t - entity type - * @param limit - limit - */ - public void setEntityLimit(EntityType t, int limit) { - getEntityLimits().put(t, limit); - setChanged(); - } - - /** - * @param entityLimits the entityLimits to set - */ - public void setEntityLimits(Map entityLimits) { - this.entityLimits = entityLimits; - setChanged(); - } - - /** - * Set an offset to an entity limit. This will increase/decrease the value of - * the limit. - * - * @param type Entity Type - * @param entityLimitsOffset the entityLimitsOffset to set - */ - public void setEntityLimitsOffset(EntityType type, Integer entityLimitsOffset) { - this.getEntityLimitsOffset().put(type, entityLimitsOffset); - } - - /** - * @param gameMode the gameMode to set - */ - public void setGameMode(String gameMode) { - this.gameMode = gameMode; - setChanged(); - } - - /* - * (non-Javadoc) - * - * @see - * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang. - * String) - */ - @Override - public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; - setChanged(); - } -} +package world.bentobox.limits.objects; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; + +/** + * @author tastybento + * + */ +@Table(name = "IslandBlockCount") +public class IslandBlockCount implements DataObject { + + @Expose + private Map blockCounts = new EnumMap<>(Material.class); + + /** + * Permission based limits + */ + @Expose + private Map blockLimits = new EnumMap<>(Material.class); + + @Expose + private Map blockLimitsOffset = new EnumMap<>(Material.class); + + private boolean changed; + + @Expose + private Map entityGroupLimits = new HashMap<>(); + @Expose + private Map entityGroupLimitsOffset = new HashMap<>(); + @Expose + private Map entityLimits = new EnumMap<>(EntityType.class); + @Expose + private Map entityLimitsOffset = new EnumMap<>(EntityType.class); + @Expose + private String gameMode; + @Expose + private String uniqueId; + + /** + * Create an island block count object + * + * @param islandId - unique Island ID string + * @param gameMode - Game mode name from gm.getDescription().getName() + */ + public IslandBlockCount(String islandId, String gameMode) { + this.uniqueId = islandId; + this.gameMode = gameMode; + setChanged(); + } + + /** + * Add a material to the count + * + * @param material - material + */ + public void add(Material material) { + getBlockCounts().merge(material, 1, Integer::sum); + setChanged(); + } + + /** + * Clear all island-specific entity group limits + */ + public void clearEntityGroupLimits() { + entityGroupLimits.clear(); + setChanged(); + } + + /** + * Clear all island-specific entity type limits + */ + public void clearEntityLimits() { + entityLimits.clear(); + setChanged(); + } + + /** + * Get the block count for this material for this island + * + * @param m - material + * @return count + */ + public Integer getBlockCount(Material m) { + return getBlockCounts().getOrDefault(m, 0); + } + + /** + * @return the blockCount + */ + public Map getBlockCounts() { + if (blockCounts == null) { + blockCounts = new EnumMap<>(Material.class); + } + return blockCounts; + } + + /** + * Get the block limit for this material for this island + * + * @param m - material + * @return limit or -1 for unlimited + */ + public int getBlockLimit(Material m) { + return getBlockLimits().getOrDefault(m, -1); + } + + /** + * Get the block offset for this material for this island + * + * @param m - material + * @return offset + */ + public int getBlockLimitOffset(Material m) { + return getBlockLimitsOffset().getOrDefault(m, 0); + } + + /** + * @return the blockLimits + */ + public Map getBlockLimits() { + return Objects.requireNonNullElse(blockLimits, new EnumMap<>(Material.class)); + } + + /** + * @return the blockLimitsOffset + */ + public Map getBlockLimitsOffset() { + if (blockLimitsOffset == null) { + blockLimitsOffset = new EnumMap<>(Material.class); + } + return blockLimitsOffset; + } + + /** + * Get the limit for an entity group + * + * @param name - entity group + * @return limit or -1 for unlimited + */ + public int getEntityGroupLimit(String name) { + return getEntityGroupLimits().getOrDefault(name, -1); + } + + /** + * Get the offset for an entity group + * + * @param name - entity group + * @return offset + */ + public int getEntityGroupLimitOffset(String name) { + return getEntityGroupLimitsOffset().getOrDefault(name, 0); + } + + /** + * @return the entityGroupLimits + */ + public Map getEntityGroupLimits() { + return Objects.requireNonNullElse(entityGroupLimits, new HashMap<>()); + } + + /** + * @return the entityGroupLimitsOffset + */ + public Map getEntityGroupLimitsOffset() { + if (entityGroupLimitsOffset == null) { + entityGroupLimitsOffset = new HashMap<>(); + } + return entityGroupLimitsOffset; + } + + /** + * Get the limit for an entity type + * + * @param t - entity type + * @return limit or -1 for unlimited + */ + public int getEntityLimit(EntityType t) { + return getEntityLimits().getOrDefault(t, -1); + } + + /** + * Get the limit offset for an entity type + * + * @param t - entity type + * @return offset + */ + public int getEntityLimitOffset(EntityType t) { + return getEntityLimitsOffset().getOrDefault(t, 0); + } + + /** + * @return the entityLimits + */ + public Map getEntityLimits() { + return Objects.requireNonNullElse(entityLimits, new EnumMap<>(EntityType.class)); + } + + /** + * @return the entityLimitsOffset + */ + public Map getEntityLimitsOffset() { + if (entityLimitsOffset == null) { + entityLimitsOffset = new EnumMap<>(EntityType.class); + } + return entityLimitsOffset; + } + + /** + * @return the gameMode + */ + public String getGameMode() { + return gameMode; + } + + /* + * (non-Javadoc) + * + * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() + */ + @Override + public String getUniqueId() { + return uniqueId; + } + + /** + * Check if no more of this material can be added to this island + * + * @param m - material + * @return true if no more material can be added + */ + public boolean isAtLimit(Material m) { + // Check island limits first + return getBlockLimits().containsKey(m) + && getBlockCounts().getOrDefault(m, 0) >= getBlockLimit(m) + this.getBlockLimitOffset(m); + } + + /** + * Check if this material is at or over a limit + * + * @param material - block material + * @param limit - limit to check + * @return true if count is >= limit + */ + public boolean isAtLimit(Material material, int limit) { + return getBlockCounts().getOrDefault(material, 0) >= limit + this.getBlockLimitOffset(material); + } + + public boolean isBlockLimited(Material m) { + return getBlockLimits().containsKey(m); + } + + /** + * @return the changed + */ + public boolean isChanged() { + return changed; + } + + public boolean isGameMode(String gameMode) { + return getGameMode().equals(gameMode); + } + + /** + * Remove a material from the count + * + * @param material - material + */ + public void remove(Material material) { + getBlockCounts().put(material, getBlockCounts().getOrDefault(material, 0) - 1); + getBlockCounts().values().removeIf(v -> v <= 0); + setChanged(); + } + + /** + * @param blockCounts the blockCount to set + */ + public void setBlockCounts(Map blockCounts) { + this.blockCounts = blockCounts; + setChanged(); + } + + /** + * Set the block limit for this material for this island + * + * @param m - material + * @param limit - maximum number allowed + */ + public void setBlockLimit(Material m, int limit) { + getBlockLimits().put(m, limit); + setChanged(); + } + + /** + * @param blockLimits the blockLimits to set + */ + public void setBlockLimits(Map blockLimits) { + this.blockLimits = blockLimits; + setChanged(); + } + + /** + * Set an offset to a block limit. This will increase/decrease the value of the + * limit. + * + * @param m material + * @param blockLimitsOffset the blockLimitsOffset to set + */ + public void setBlockLimitsOffset(Material m, Integer blockLimitsOffset) { + getBlockLimitsOffset().put(m, blockLimitsOffset); + } + + /** + * Mark changed + */ + public void setChanged() { + this.changed = true; + } + + /** + * @param changed the changed to set + */ + public void setChanged(boolean changed) { + this.changed = changed; + } + + /** + * Set an island-specific entity group limit + * + * @param name - entity group + * @param limit - limit + */ + public void setEntityGroupLimit(String name, int limit) { + getEntityGroupLimits().put(name, limit); + setChanged(); + } + + /** + * @param entityGroupLimits the entityGroupLimits to set + */ + public void setEntityGroupLimits(Map entityGroupLimits) { + this.entityGroupLimits = entityGroupLimits; + setChanged(); + } + + /** + * Set an offset to an entity group limit. This will increase/decrease the value + * of the limit. + * + * @param name group name + * @param entityGroupLimitsOffset the entityGroupLimitsOffset to set + */ + public void setEntityGroupLimitsOffset(String name, Integer entityGroupLimitsOffset) { + getEntityGroupLimitsOffset().put(name, entityGroupLimitsOffset); + } + + /** + * Set an island-specific entity type limit + * + * @param t - entity type + * @param limit - limit + */ + public void setEntityLimit(EntityType t, int limit) { + getEntityLimits().put(t, limit); + setChanged(); + } + + /** + * @param entityLimits the entityLimits to set + */ + public void setEntityLimits(Map entityLimits) { + this.entityLimits = entityLimits; + setChanged(); + } + + /** + * Set an offset to an entity limit. This will increase/decrease the value of + * the limit. + * + * @param type Entity Type + * @param entityLimitsOffset the entityLimitsOffset to set + */ + public void setEntityLimitsOffset(EntityType type, Integer entityLimitsOffset) { + this.getEntityLimitsOffset().put(type, entityLimitsOffset); + } + + /** + * @param gameMode the gameMode to set + */ + public void setGameMode(String gameMode) { + this.gameMode = gameMode; + setChanged(); + } + + /* + * (non-Javadoc) + * + * @see + * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang. + * String) + */ + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + setChanged(); + } +} diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index 9689b58..753b7a5 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -1,22 +1,22 @@ -name: Limits -main: world.bentobox.limits.Limits -version: ${version}${build.number} -api-version: 2.7.1 - -authors: tastybento - -softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid - -permissions: - '[gamemode].limits.player.limits': - description: Player can use limits command - default: true - '[gamemode].limits.player.recount': - description: Player can use recount command - default: true - '[gamemode].limits.admin.limits': - description: Player can use admin limits command - default: op - '[gamemode].mod.bypass': - description: Player can bypass limits - default: op +name: Limits +main: world.bentobox.limits.Limits +version: ${version}${build.number} +api-version: 2.7.1 + +authors: tastybento + +softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid + +permissions: + '[gamemode].limits.player.limits': + description: Player can use limits command + default: true + '[gamemode].limits.player.recount': + description: Player can use recount command + default: true + '[gamemode].limits.admin.limits': + description: Player can use admin limits command + default: op + '[gamemode].mod.bypass': + description: Player can bypass limits + default: op diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 41b1b0f..01d7ebf 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,130 +1,130 @@ -# Game Modes covered by limits -gamemodes: -- AcidIsland -- BSkyBlock -- CaveBlock - -# Ignore this island's center block. For most worlds, this is bedrock, but for AOneBlock it is -# the magic block, so ignoring it from limits makes sense. -ignore-center-block: true - -# Permissions -# Island owners can be given permissions that override all general settings -# Format is GAME-MODE-NAME.island.limit.MATERIAL.LIMIT -# example: bskyblock.island.limit.hopper.10 -# permission activates when player logs in. -# -# Cooldown for player recount command in seconds -cooldown: 120 - -# Use async checks for snowmen and iron golums. Set to false if you see problems. -async-golums: true - -# General block limiting -# Use this section to limit how many blocks can be added to an island. -# 0 means the item will be blocked from placement completely. -# These limits apply to every game mode world -blocklimits: - HOPPER: 10 -# This section is for world-specific limits and overrides the general limit -# Specify each world you want to limit individually (including nether and end worlds) -# If these worlds are not covered by the game modes above, nothing will happen -worlds: - AcidIsland_world: - HOPPER: 11 - -# Default entity limits within a player's island space (protected area and to island limit). -# A limit of 5 will allow up to 5 entities in over world, 5 in nether and 5 in the end. -# Affects all types of creature spawning. Also includes entities like MINECARTS. -# Note: Only the first 49 limited blocks and entities are shown in the limits GUI. -entitylimits: - ENDERMAN: 5 - CHICKEN: 10 - -# Entity Groups -# Name the group anything you like -# Select an icon, which is a Bukkit Material -# Select the limit for the total group -# List the entities in the group using Bukkit EntityTypes -entitygrouplimits: - Monsters: - icon: ROTTEN_FLESH - limit: 250 - entities: - - SKELETON - - SILVERFISH - - STRAY - - ZOMBIE_VILLAGER - - WITHER - - WARDEN - - BLAZE - - DROWNED - - BREEZE - - ZOMBIFIED_PIGLIN - - EVOKER - - PILLAGER - - HUSK - - CREEPER - - VINDICATOR - - ZOMBIE - - ENDERMAN - - ELDER_GUARDIAN - - WITHER_SKELETON - - CAVE_SPIDER - - GUARDIAN - - RAVAGER - - PIGLIN - - BOGGED - - WITCH - - ENDERMITE - - ZOGLIN - - PIGLIN_BRUTE - - ILLUSIONER - - SPIDER - - VEX - Animals: - icon: SADDLE - limit: 200 - entities: - - SHEEP - - AXOLOTL - - DONKEY - - MOOSHROOM - - TRADER_LLAMA - - BEE - - HORSE - - ZOMBIE_HORSE - - PIG - - PARROT - - CHICKEN - - RABBIT - - SNIFFER - - FROG - - GOAT - - PANDA - - CAMEL - - STRIDER - - TURTLE - - CAT - - SKELETON_HORSE - - COW - - LLAMA - - ARMADILLO - - HOGLIN - - POLAR_BEAR - - WOLF - - MULE - - OCELOT - - FOX - - DOLPHIN - - COD - - PUFFERFISH - - TADPOLE - - SQUID - - SALMON - - TROPICAL_FISH - - GLOW_SQUID - - - - +# Game Modes covered by limits +gamemodes: +- AcidIsland +- BSkyBlock +- CaveBlock + +# Ignore this island's center block. For most worlds, this is bedrock, but for AOneBlock it is +# the magic block, so ignoring it from limits makes sense. +ignore-center-block: true + +# Permissions +# Island owners can be given permissions that override all general settings +# Format is GAME-MODE-NAME.island.limit.MATERIAL.LIMIT +# example: bskyblock.island.limit.hopper.10 +# permission activates when player logs in. +# +# Cooldown for player recount command in seconds +cooldown: 120 + +# Use async checks for snowmen and iron golums. Set to false if you see problems. +async-golums: true + +# General block limiting +# Use this section to limit how many blocks can be added to an island. +# 0 means the item will be blocked from placement completely. +# These limits apply to every game mode world +blocklimits: + HOPPER: 10 +# This section is for world-specific limits and overrides the general limit +# Specify each world you want to limit individually (including nether and end worlds) +# If these worlds are not covered by the game modes above, nothing will happen +worlds: + AcidIsland_world: + HOPPER: 11 + +# Default entity limits within a player's island space (protected area and to island limit). +# A limit of 5 will allow up to 5 entities in over world, 5 in nether and 5 in the end. +# Affects all types of creature spawning. Also includes entities like MINECARTS. +# Note: Only the first 49 limited blocks and entities are shown in the limits GUI. +entitylimits: + ENDERMAN: 5 + CHICKEN: 10 + +# Entity Groups +# Name the group anything you like +# Select an icon, which is a Bukkit Material +# Select the limit for the total group +# List the entities in the group using Bukkit EntityTypes +entitygrouplimits: + Monsters: + icon: ROTTEN_FLESH + limit: 250 + entities: + - SKELETON + - SILVERFISH + - STRAY + - ZOMBIE_VILLAGER + - WITHER + - WARDEN + - BLAZE + - DROWNED + - BREEZE + - ZOMBIFIED_PIGLIN + - EVOKER + - PILLAGER + - HUSK + - CREEPER + - VINDICATOR + - ZOMBIE + - ENDERMAN + - ELDER_GUARDIAN + - WITHER_SKELETON + - CAVE_SPIDER + - GUARDIAN + - RAVAGER + - PIGLIN + - BOGGED + - WITCH + - ENDERMITE + - ZOGLIN + - PIGLIN_BRUTE + - ILLUSIONER + - SPIDER + - VEX + Animals: + icon: SADDLE + limit: 200 + entities: + - SHEEP + - AXOLOTL + - DONKEY + - MOOSHROOM + - TRADER_LLAMA + - BEE + - HORSE + - ZOMBIE_HORSE + - PIG + - PARROT + - CHICKEN + - RABBIT + - SNIFFER + - FROG + - GOAT + - PANDA + - CAMEL + - STRIDER + - TURTLE + - CAT + - SKELETON_HORSE + - COW + - LLAMA + - ARMADILLO + - HOGLIN + - POLAR_BEAR + - WOLF + - MULE + - OCELOT + - FOX + - DOLPHIN + - COD + - PUFFERFISH + - TADPOLE + - SQUID + - SALMON + - TROPICAL_FISH + - GLOW_SQUID + + + + diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index e6137f9..bab9a97 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] omezen na [number]!" -entity-limits: - hit-limit: "&cSpawnování [entity] omezeno na [number]!" -limits: - panel-title: Omezení ostrovů -admin: - limits: - main: - parameters: "" - description: ukázat omezení ostrova hráče - calc: - parameters: "" - description: přepočítat omezení ostrova hráče - finished: "&aPřepočítání ostrova úspěšně dokončeno!" - offset: - unknown: "&c Neznámý materiál nebo entita [name]." - main: - description: umožňuje spravovat limity offsetů pro materiály a entity - set: - parameters: " <číslo>" - description: nastaví nový offset pro limit materiálu nebo entity - success: "&a Mezní posun pro [name] je nastaven na [number]." - same: "&c Mezní posun pro [name] je již [number]." - add: - parameters: " <číslo>" - description: přidá offset pro limit materiálu nebo entity - success: "&a Posun limitu pro [name] se zvýší na [number]." - remove: - parameters: " <číslo>" - description: snižuje offset pro limit materiálu nebo entity - success: "&a Posun limitu pro [name] se sníží na [number]." - reset: - parameters: " " - description: odstraní offset pro materiál nebo entitu - success: "&a Mezní posun pro [jméno] je nastaven na 0." - view: - parameters: " " - description: zobrazuje posun pro materiál nebo entitu - message: "&odsazení [name] je nastaveno na [number]." -island: - limits: - description: ukázat omezení tvého ostrova - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c V tomto světě nejsou stanovena žádná omezení" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Tento ostrov nemá vlastníka" - not-on-island: "&c Toto umístění nemá nastavena omezení." - recount: - description: přepočítá omezení tvého ostrova - now-recounting: "&b Nyní vyprávění. Může to chvíli trvat, čekejte prosím..." - in-progress: "&c Probíhá obnovení ostrova. Čekejte prosím..." - time-out: "&c Časový limit při přepočítávání. Je ostrov opravdu velký?" +--- +block-limits: + hit-limit: "&c[material] omezen na [number]!" +entity-limits: + hit-limit: "&cSpawnování [entity] omezeno na [number]!" +limits: + panel-title: Omezení ostrovů +admin: + limits: + main: + parameters: "" + description: ukázat omezení ostrova hráče + calc: + parameters: "" + description: přepočítat omezení ostrova hráče + finished: "&aPřepočítání ostrova úspěšně dokončeno!" + offset: + unknown: "&c Neznámý materiál nebo entita [name]." + main: + description: umožňuje spravovat limity offsetů pro materiály a entity + set: + parameters: " <číslo>" + description: nastaví nový offset pro limit materiálu nebo entity + success: "&a Mezní posun pro [name] je nastaven na [number]." + same: "&c Mezní posun pro [name] je již [number]." + add: + parameters: " <číslo>" + description: přidá offset pro limit materiálu nebo entity + success: "&a Posun limitu pro [name] se zvýší na [number]." + remove: + parameters: " <číslo>" + description: snižuje offset pro limit materiálu nebo entity + success: "&a Posun limitu pro [name] se sníží na [number]." + reset: + parameters: " " + description: odstraní offset pro materiál nebo entitu + success: "&a Mezní posun pro [jméno] je nastaven na 0." + view: + parameters: " " + description: zobrazuje posun pro materiál nebo entitu + message: "&odsazení [name] je nastaveno na [number]." +island: + limits: + description: ukázat omezení tvého ostrova + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c V tomto světě nejsou stanovena žádná omezení" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Tento ostrov nemá vlastníka" + not-on-island: "&c Toto umístění nemá nastavena omezení." + recount: + description: přepočítá omezení tvého ostrova + now-recounting: "&b Nyní vyprávění. Může to chvíli trvat, čekejte prosím..." + in-progress: "&c Probíhá obnovení ostrova. Čekejte prosím..." + time-out: "&c Časový limit při přepočítávání. Je ostrov opravdu velký?" diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 763f15c..5400e29 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -1,64 +1,64 @@ ---- -block-limits: - hit-limit: "&c[material] limitiert auf [number]!" -entity-limits: - hit-limit: "&c[entity] spawning limitiert auf [number]!" -limits: - panel-title: Insel Limitierungen -admin: - limits: - main: - parameters: "" - description: Zeige die Limitierungen für den Spieler an - calc: - parameters: "" - description: Berechne die Insel Limitierungen für den Spieler neu - finished: "&aInselberechnung erfolgreich abgeschlossen!" - offset: - unknown: "&c Unbekanntes Material oder Entität [name]." - main: - description: ermöglicht die Verwaltung von Grenzwertverschiebungen für Materialien - und Entitäten - set: - parameters: " " - description: legt einen neuen Offset für Material- oder Entity-Grenzwert fest - success: "&a Der Grenzoffset für [name] ist auf [number] eingestellt." - same: "&c Der Grenzoffset für [name] ist bereits [number]." - add: - parameters: " " - description: fügt einen Offset für Material- oder Entitätslimit hinzu - success: "&a Der Limit-Offset für [name] wird bis [number] erhöht." - remove: - parameters: " " - description: reduziert den Offset für Material- oder Entitätslimit - success: "&a Der Grenzoffset für [name] wird auf [number] reduziert." - reset: - parameters: " " - description: Entfernt den Offset für Material oder Entität - success: "&a Der Grenzoffset für [name] wird auf 0 gesetzt." - view: - parameters: " " - description: zeigt den Offset für Material oder Entität an - message: "&a [name]-Offset ist auf [number] gesetzt." -island: - limits: - description: Zeige deine Insel Limitierungen - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Es gibt keine Grenzen in dieser Welt" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Diese Insel hat keinen Besitzer" - not-on-island: "&c Für diesen Standort sind keine Beschränkungen festgelegt." - recount: - description: Zählt die Limitierungen für deine Insel auf - now-recounting: "&b Ich erzähle jetzt. Dies kann eine Weile dauern, bitte warten..." - in-progress: "&c Inselrückmeldung läuft. Warten Sie mal..." - time-out: "&c Zeitüberschreitung beim Erzählen. Ist die Insel wirklich so groß?" +--- +block-limits: + hit-limit: "&c[material] limitiert auf [number]!" +entity-limits: + hit-limit: "&c[entity] spawning limitiert auf [number]!" +limits: + panel-title: Insel Limitierungen +admin: + limits: + main: + parameters: "" + description: Zeige die Limitierungen für den Spieler an + calc: + parameters: "" + description: Berechne die Insel Limitierungen für den Spieler neu + finished: "&aInselberechnung erfolgreich abgeschlossen!" + offset: + unknown: "&c Unbekanntes Material oder Entität [name]." + main: + description: ermöglicht die Verwaltung von Grenzwertverschiebungen für Materialien + und Entitäten + set: + parameters: " " + description: legt einen neuen Offset für Material- oder Entity-Grenzwert fest + success: "&a Der Grenzoffset für [name] ist auf [number] eingestellt." + same: "&c Der Grenzoffset für [name] ist bereits [number]." + add: + parameters: " " + description: fügt einen Offset für Material- oder Entitätslimit hinzu + success: "&a Der Limit-Offset für [name] wird bis [number] erhöht." + remove: + parameters: " " + description: reduziert den Offset für Material- oder Entitätslimit + success: "&a Der Grenzoffset für [name] wird auf [number] reduziert." + reset: + parameters: " " + description: Entfernt den Offset für Material oder Entität + success: "&a Der Grenzoffset für [name] wird auf 0 gesetzt." + view: + parameters: " " + description: zeigt den Offset für Material oder Entität an + message: "&a [name]-Offset ist auf [number] gesetzt." +island: + limits: + description: Zeige deine Insel Limitierungen + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Es gibt keine Grenzen in dieser Welt" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Diese Insel hat keinen Besitzer" + not-on-island: "&c Für diesen Standort sind keine Beschränkungen festgelegt." + recount: + description: Zählt die Limitierungen für deine Insel auf + now-recounting: "&b Ich erzähle jetzt. Dies kann eine Weile dauern, bitte warten..." + in-progress: "&c Inselrückmeldung läuft. Warten Sie mal..." + time-out: "&c Zeitüberschreitung beim Erzählen. Ist die Insel wirklich so groß?" diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 9374b98..96c73a1 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1,68 +1,68 @@ -########################################################################################### -# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # -# the one at http://yaml-online-parser.appspot.com # -########################################################################################### - -block-limits: - hit-limit: "&c[material] limited to [number]!" -entity-limits: - hit-limit: "&c[entity] spawning limited to [number]!" -limits: - panel-title: "Island limits" - -admin: - limits: - main: - parameters: "" - description: "show the island limits for player" - calc: - parameters: "" - description: "recalculate the island limits for player" - finished: "&a Island recalc finished successfully!" - offset: - unknown: "&c Unknown material or entity [name]." - description: "allows to manage limits offsets for materials and entities" - set: - parameters: " " - description: "sets new offset for material or entity limit" - success: "&a Limit offset for [name] is set to [number]." - same: "&c Limit offset for [name] is already [number]." - add: - parameters: " " - description: "adds offset for material or entity limit" - success: "&a Limit offset for [name] is increased till [number]." - remove: - parameters: " " - description: "reduces offset for material or entity limit" - success: "&a Limit offset for [name] is reduced till [number]." - reset: - parameters: " " - description: "removes offset for material or entity" - success: "&a Limit offset for [name] is set to 0." - view: - parameters: " " - description: "displays offset for material or entity" - message: "&a [name] offset is set to [number]." -island: - limits: - description: "show your island limits" - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c No limits set in this world" - panel: - title-syntax: '[title] [sort]' - entity-group-name-syntax: '[name]' - entity-name-syntax: '[name]' - block-name-syntax: '[name]' - A2Z: "a > z" - Z2A: "z > a" - errors: - no-owner: "&c That island has no owner" - not-on-island: "&c This location does not have limits set." - recount: - description: "recounts limits for your island" - now-recounting: "&b Now recounting. This could take a while, please wait..." - in-progress: "&c Island recound is in progress. Please wait..." - time-out: "&c Time out when recounting. Is the island really big?" - +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +block-limits: + hit-limit: "&c[material] limited to [number]!" +entity-limits: + hit-limit: "&c[entity] spawning limited to [number]!" +limits: + panel-title: "Island limits" + +admin: + limits: + main: + parameters: "" + description: "show the island limits for player" + calc: + parameters: "" + description: "recalculate the island limits for player" + finished: "&a Island recalc finished successfully!" + offset: + unknown: "&c Unknown material or entity [name]." + description: "allows to manage limits offsets for materials and entities" + set: + parameters: " " + description: "sets new offset for material or entity limit" + success: "&a Limit offset for [name] is set to [number]." + same: "&c Limit offset for [name] is already [number]." + add: + parameters: " " + description: "adds offset for material or entity limit" + success: "&a Limit offset for [name] is increased till [number]." + remove: + parameters: " " + description: "reduces offset for material or entity limit" + success: "&a Limit offset for [name] is reduced till [number]." + reset: + parameters: " " + description: "removes offset for material or entity" + success: "&a Limit offset for [name] is set to 0." + view: + parameters: " " + description: "displays offset for material or entity" + message: "&a [name] offset is set to [number]." +island: + limits: + description: "show your island limits" + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c No limits set in this world" + panel: + title-syntax: '[title] [sort]' + entity-group-name-syntax: '[name]' + entity-name-syntax: '[name]' + block-name-syntax: '[name]' + A2Z: "a > z" + Z2A: "z > a" + errors: + no-owner: "&c That island has no owner" + not-on-island: "&c This location does not have limits set." + recount: + description: "recounts limits for your island" + now-recounting: "&b Now recounting. This could take a while, please wait..." + in-progress: "&c Island recound is in progress. Please wait..." + time-out: "&c Time out when recounting. Is the island really big?" + diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index bb5f378..89a4ce4 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -1,65 +1,65 @@ ---- -block-limits: - hit-limit: "&c[material] limitado a [number]!" -entity-limits: - hit-limit: "¡Aparición de &c[entity] limitada a [number]!" -limits: - panel-title: Límites de la isla -admin: - limits: - main: - parameters: "" - description: mostrar los límites de la isla para el jugador - calc: - parameters: "" - description: recalcular los límites de la isla para el jugador - finished: "&a ¡El recálculo de la Isla terminó con éxito!" - offset: - unknown: "&c Material o entidad desconocida [name]." - main: - description: permite gestionar compensaciones de límites para materiales y - entidades - set: - parameters: " " - description: establece una nueva compensación para el límite de material o - entidad - success: "&a El desplazamiento límite para [name] está establecido en [number]." - same: "&c El límite de compensación para [name] ya es [number]." - add: - parameters: " " - description: agrega compensación por límite de material o entidad - success: "&a El límite de compensación para [name] aumenta hasta [number]." - remove: - parameters: " " - description: reduce la compensación por límite de material o entidad - success: "&a La compensación límite para [name] se reduce hasta [number]." - reset: - parameters: " " - description: elimina la compensación para material o entidad - success: "&a El desplazamiento límite para [number] está establecido en 0." - view: - parameters: " " - description: muestra el desplazamiento para material o entidad - message: "& desplazamiento de [name] se establece en [number]." -island: - limits: - description: muestra los límites de tu isla - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c No hay límites establecidos en este mundo." - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Esa isla no tiene dueño" - not-on-island: "&c Esta ubicación no tiene límites establecidos." - recount: - description: cuenta los límites para tu isla - now-recounting: "&b Ahora contando. Esto podría tomar un tiempo, por favor espere..." - in-progress: "&c El recuento de la isla está en progreso. Espere por favor..." - time-out: "&c Ha pasado demasiado tiempo contando. ¿La isla es realmente grande?" +--- +block-limits: + hit-limit: "&c[material] limitado a [number]!" +entity-limits: + hit-limit: "¡Aparición de &c[entity] limitada a [number]!" +limits: + panel-title: Límites de la isla +admin: + limits: + main: + parameters: "" + description: mostrar los límites de la isla para el jugador + calc: + parameters: "" + description: recalcular los límites de la isla para el jugador + finished: "&a ¡El recálculo de la Isla terminó con éxito!" + offset: + unknown: "&c Material o entidad desconocida [name]." + main: + description: permite gestionar compensaciones de límites para materiales y + entidades + set: + parameters: " " + description: establece una nueva compensación para el límite de material o + entidad + success: "&a El desplazamiento límite para [name] está establecido en [number]." + same: "&c El límite de compensación para [name] ya es [number]." + add: + parameters: " " + description: agrega compensación por límite de material o entidad + success: "&a El límite de compensación para [name] aumenta hasta [number]." + remove: + parameters: " " + description: reduce la compensación por límite de material o entidad + success: "&a La compensación límite para [name] se reduce hasta [number]." + reset: + parameters: " " + description: elimina la compensación para material o entidad + success: "&a El desplazamiento límite para [number] está establecido en 0." + view: + parameters: " " + description: muestra el desplazamiento para material o entidad + message: "& desplazamiento de [name] se establece en [number]." +island: + limits: + description: muestra los límites de tu isla + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c No hay límites establecidos en este mundo." + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Esa isla no tiene dueño" + not-on-island: "&c Esta ubicación no tiene límites establecidos." + recount: + description: cuenta los límites para tu isla + now-recounting: "&b Ahora contando. Esto podría tomar un tiempo, por favor espere..." + in-progress: "&c El recuento de la isla está en progreso. Espere por favor..." + time-out: "&c Ha pasado demasiado tiempo contando. ¿La isla es realmente grande?" diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index dc5d18b..6c1891f 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -1,66 +1,66 @@ ---- -block-limits: - hit-limit: "&c[material] limité à [number]!" -entity-limits: - hit-limit: "&c[entity] spawning limited to [number]!" -limits: - panel-title: Limites de l'île -admin: - limits: - main: - parameters: "" - description: affiche les limites de l'île pour le joueur - calc: - parameters: "" - description: recalcule les limites de l'île pour le joueur - finished: "&a Recomptage terminé avec succès!" - offset: - unknown: "&c Matériau ou entité inconnu [name]." - main: - description: permet de gérer les décalages de limites pour les matériaux et - les entités - set: - parameters: " " - description: définit un nouveau décalage pour la limite de matériau ou d'entité - success: "&a Le décalage limite pour [name] est défini sur [number]." - same: "&c Le décalage limite pour [name] est déjà [number]." - add: - parameters: " " - description: ajoute un décalage pour la limite de matériau ou d'entité - success: "&a Le décalage limite pour [name] est augmenté jusqu'à [number]." - remove: - parameters: " " - description: réduit le décalage pour la limite de matériau ou d'entité - success: "&a Le décalage limite pour [name] est réduit jusqu'à [number]." - reset: - parameters: " " - description: supprime le décalage pour le matériau ou l'entité - success: "&a Le décalage limite pour [name] est défini sur 0." - view: - parameters: " " - description: affiche le décalage pour le matériau ou l'entité - message: "&a [name] le décalage est défini sur [number]." -island: - limits: - description: affichez les limites de votre île - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Aucune limite n'est fixée dans ce monde" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Cette île n'a pas de propriétaire" - not-on-island: "&c Cet emplacement n'a pas de limites définies." - recount: - description: recompte les limites de votre île - now-recounting: "&b Recomptage en cours. Cela peut prendre un certain temps, - veuillez patienter..." - in-progress: "&c Le recomptage de l'île est en cours. Veuillez patienter s'il - vous plaît..." - time-out: "&c Time out lors du recomptage. L'île est-elle vraiment grande?" +--- +block-limits: + hit-limit: "&c[material] limité à [number]!" +entity-limits: + hit-limit: "&c[entity] spawning limited to [number]!" +limits: + panel-title: Limites de l'île +admin: + limits: + main: + parameters: "" + description: affiche les limites de l'île pour le joueur + calc: + parameters: "" + description: recalcule les limites de l'île pour le joueur + finished: "&a Recomptage terminé avec succès!" + offset: + unknown: "&c Matériau ou entité inconnu [name]." + main: + description: permet de gérer les décalages de limites pour les matériaux et + les entités + set: + parameters: " " + description: définit un nouveau décalage pour la limite de matériau ou d'entité + success: "&a Le décalage limite pour [name] est défini sur [number]." + same: "&c Le décalage limite pour [name] est déjà [number]." + add: + parameters: " " + description: ajoute un décalage pour la limite de matériau ou d'entité + success: "&a Le décalage limite pour [name] est augmenté jusqu'à [number]." + remove: + parameters: " " + description: réduit le décalage pour la limite de matériau ou d'entité + success: "&a Le décalage limite pour [name] est réduit jusqu'à [number]." + reset: + parameters: " " + description: supprime le décalage pour le matériau ou l'entité + success: "&a Le décalage limite pour [name] est défini sur 0." + view: + parameters: " " + description: affiche le décalage pour le matériau ou l'entité + message: "&a [name] le décalage est défini sur [number]." +island: + limits: + description: affichez les limites de votre île + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Aucune limite n'est fixée dans ce monde" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Cette île n'a pas de propriétaire" + not-on-island: "&c Cet emplacement n'a pas de limites définies." + recount: + description: recompte les limites de votre île + now-recounting: "&b Recomptage en cours. Cela peut prendre un certain temps, + veuillez patienter..." + in-progress: "&c Le recomptage de l'île est en cours. Veuillez patienter s'il + vous plaît..." + time-out: "&c Time out lors du recomptage. L'île est-elle vraiment grande?" diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 4597eee..5c10936 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] ograničen na [number]!" -entity-limits: - hit-limit: "&c[entity] stvaranje ograničeno na [number]!" -limits: - panel-title: Granice otoka -admin: - limits: - main: - parameters: "" - description: pokazati ograničenja otoka za igrača - calc: - parameters: "" - description: ponovno izračunati ograničenja otoka za igrača - finished: "&a Recalc otoka uspješno završen!" - offset: - unknown: "&c Nepoznati materijal ili entitet [name]." - main: - description: omogućuje upravljanje pomacima ograničenja za materijale i entitete - set: - parameters: " " - description: postavlja novi pomak za ograničenje materijala ili entiteta - success: "&a Ograničenje pomaka za [name] postavljeno je na [number]." - same: "&c Ograničenje pomaka za [name] već je [number]." - add: - parameters: " " - description: dodaje pomak za ograničenje materijala ili entiteta - success: "&a Ograničenje pomaka za [name] povećava se do [number]." - remove: - parameters: " " - description: smanjuje pomak za ograničenje materijala ili entiteta - success: "&a Ograničenje pomaka za [name] smanjeno je do [number]." - reset: - parameters: " " - description: uklanja pomak za materijal ili entitet - success: "&a Ograničenje pomaka za [name] postavljeno je na 0." - view: - parameters: " " - description: prikazuje pomak za materijal ili entitet - message: "&pomak [name] postavljen je na [number]." -island: - limits: - description: pokazati granice vašeg otoka - max-color: i c - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c U ovom svijetu nema ograničenja" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Taj otok nema vlasnika" - not-on-island: "&c Ova lokacija nema postavljena ograničenja." - recount: - description: preračunava ograničenja za vaš otok - now-recounting: "&b Sada prepričavam. Ovo bi moglo potrajati, pričekajte..." - in-progress: "&c Otok je u tijeku. Molimo pričekajte..." - time-out: "&c Time out kod prepričavanja. Je li otok stvarno velik?" +--- +block-limits: + hit-limit: "&c[material] ograničen na [number]!" +entity-limits: + hit-limit: "&c[entity] stvaranje ograničeno na [number]!" +limits: + panel-title: Granice otoka +admin: + limits: + main: + parameters: "" + description: pokazati ograničenja otoka za igrača + calc: + parameters: "" + description: ponovno izračunati ograničenja otoka za igrača + finished: "&a Recalc otoka uspješno završen!" + offset: + unknown: "&c Nepoznati materijal ili entitet [name]." + main: + description: omogućuje upravljanje pomacima ograničenja za materijale i entitete + set: + parameters: " " + description: postavlja novi pomak za ograničenje materijala ili entiteta + success: "&a Ograničenje pomaka za [name] postavljeno je na [number]." + same: "&c Ograničenje pomaka za [name] već je [number]." + add: + parameters: " " + description: dodaje pomak za ograničenje materijala ili entiteta + success: "&a Ograničenje pomaka za [name] povećava se do [number]." + remove: + parameters: " " + description: smanjuje pomak za ograničenje materijala ili entiteta + success: "&a Ograničenje pomaka za [name] smanjeno je do [number]." + reset: + parameters: " " + description: uklanja pomak za materijal ili entitet + success: "&a Ograničenje pomaka za [name] postavljeno je na 0." + view: + parameters: " " + description: prikazuje pomak za materijal ili entitet + message: "&pomak [name] postavljen je na [number]." +island: + limits: + description: pokazati granice vašeg otoka + max-color: i c + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c U ovom svijetu nema ograničenja" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Taj otok nema vlasnika" + not-on-island: "&c Ova lokacija nema postavljena ograničenja." + recount: + description: preračunava ograničenja za vaš otok + now-recounting: "&b Sada prepričavam. Ovo bi moglo potrajati, pričekajte..." + in-progress: "&c Otok je u tijeku. Molimo pričekajte..." + time-out: "&c Time out kod prepričavanja. Je li otok stvarno velik?" diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index 87d95fa..9b3a760 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] limitálva ennyire [number]!" -entity-limits: - hit-limit: "&c[entity] idézés limitálva ennyire [number]!" -limits: - panel-title: Sziget limitek -admin: - limits: - main: - parameters: "" - description: Egy játékos sziget limitjének megtekintése - calc: - parameters: "" - description: Egy játékos sziget limitjének újraszámolása - finished: "&aA Sziget újraszámolás sikeresen befejeződött!" - offset: - unknown: "&c Ismeretlen anyag vagy entitás [name]." - main: - description: lehetővé teszi az anyagok és entitások limiteltolásainak kezelését - set: - parameters: " " - description: új eltolást állít be az anyag- vagy entitáskorláthoz - success: "&a A [name] határeltolódása [number] értékre van állítva." - same: "&c A [name] korláteltolása már [number]." - add: - parameters: " " - description: eltolást ad hozzá az anyag- vagy entitáskorláthoz - success: "&a A [name] határeltolódása a [number] értékig nő." - remove: - parameters: " " - description: csökkenti az anyag- vagy entitáskorlát ellentételezését - success: "&a A [name] limiteltolása [number] értékre csökken." - reset: - parameters: " " - description: eltávolítja az anyag vagy entitás eltolását - success: "&a A [name] határeltolódása 0-ra van állítva." - view: - parameters: " " - description: anyag vagy entitás eltolását jeleníti meg - message: "&a [name] eltolás értéke [number]." -island: - limits: - description: Sziget limitek megtekintése - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Nincsenek korlátok ezen a világon" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Annak a szigetnek nincs gazdája" - not-on-island: "&c Ennek a helynek nincs korlátja beállítva." - recount: - description: Limitek újraszámolása a szigeteden - now-recounting: "&b Most mesélünk. Ez eltarthat egy ideig, kérem, várjon..." - in-progress: "&c Sziget visszaállítása folyamatban van. Kérem, várjon..." - time-out: "&c Időtúllépés az újraszámláláskor. Tényleg nagy a sziget?" +--- +block-limits: + hit-limit: "&c[material] limitálva ennyire [number]!" +entity-limits: + hit-limit: "&c[entity] idézés limitálva ennyire [number]!" +limits: + panel-title: Sziget limitek +admin: + limits: + main: + parameters: "" + description: Egy játékos sziget limitjének megtekintése + calc: + parameters: "" + description: Egy játékos sziget limitjének újraszámolása + finished: "&aA Sziget újraszámolás sikeresen befejeződött!" + offset: + unknown: "&c Ismeretlen anyag vagy entitás [name]." + main: + description: lehetővé teszi az anyagok és entitások limiteltolásainak kezelését + set: + parameters: " " + description: új eltolást állít be az anyag- vagy entitáskorláthoz + success: "&a A [name] határeltolódása [number] értékre van állítva." + same: "&c A [name] korláteltolása már [number]." + add: + parameters: " " + description: eltolást ad hozzá az anyag- vagy entitáskorláthoz + success: "&a A [name] határeltolódása a [number] értékig nő." + remove: + parameters: " " + description: csökkenti az anyag- vagy entitáskorlát ellentételezését + success: "&a A [name] limiteltolása [number] értékre csökken." + reset: + parameters: " " + description: eltávolítja az anyag vagy entitás eltolását + success: "&a A [name] határeltolódása 0-ra van állítva." + view: + parameters: " " + description: anyag vagy entitás eltolását jeleníti meg + message: "&a [name] eltolás értéke [number]." +island: + limits: + description: Sziget limitek megtekintése + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Nincsenek korlátok ezen a világon" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Annak a szigetnek nincs gazdája" + not-on-island: "&c Ennek a helynek nincs korlátja beállítva." + recount: + description: Limitek újraszámolása a szigeteden + now-recounting: "&b Most mesélünk. Ez eltarthat egy ideig, kérem, várjon..." + in-progress: "&c Sziget visszaállítása folyamatban van. Kérem, várjon..." + time-out: "&c Időtúllépés az újraszámláláskor. Tényleg nagy a sziget?" diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 3f479a8..c365d9c 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -1,65 +1,65 @@ ---- -block-limits: - hit-limit: "&c[material] dibatasi sampai [number]!" -entity-limits: - hit-limit: "&c[entity] spawning dibatasi sampai [number]!" -limits: - panel-title: Batasan Pulau -admin: - limits: - main: - parameters: "" - description: menampilkan batasan pulau untuk pemain - calc: - parameters: "" - description: menghitung ulang batasan pulau untuk pemain - finished: "&aPenghitungan ulang pulau selesai tanpa masalah!" - offset: - unknown: "&c Material atau entitas tidak dikenal [name]." - main: - description: memungkinkan untuk mengelola batas offset untuk material dan - entitas - set: - parameters: " " - description: menetapkan offset baru untuk batasan material atau entitas - success: "&a Batas offset untuk [name] ditetapkan ke [number]." - same: "&a Batas offset untuk [name] ditetapkan ke [number]." - add: - parameters: " " - description: menambahkan offset untuk batasan material atau entitas - success: "&a Batas offset untuk [name] ditingkatkan hingga [number]." - remove: - parameters: " " - description: mengurangi offset untuk batas material atau entitas - success: "&a Batas offset untuk [name] dikurangi hingga [number]." - reset: - parameters: " " - description: menghapus offset untuk material atau entitas - success: "&a Batas offset untuk [name] ditetapkan ke 0." - view: - parameters: " " - description: menampilkan offset untuk material atau entitas - message: "&a [name] offset diatur ke [number]." -island: - limits: - description: menampilkan batasan pulau kamu - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Tidak ada batasan yang ditetapkan di dunia ini" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Pulau itu tidak memiliki pemilik" - not-on-island: "&c Lokasi ini tidak memiliki batasan yang ditetapkan." - recount: - description: menghitung ulang batasan untuk pulau kamu - now-recounting: "&b Menghitung ulang. Membutuhkan waktu beberapa saat, silahkan - tunggu..." - in-progress: "&c Perhitungan pulau sedang diproses. Silahkan tunggu..." - time-out: "&c Gagal menghitung ulang. Apakah pulau terlalu besar?" +--- +block-limits: + hit-limit: "&c[material] dibatasi sampai [number]!" +entity-limits: + hit-limit: "&c[entity] spawning dibatasi sampai [number]!" +limits: + panel-title: Batasan Pulau +admin: + limits: + main: + parameters: "" + description: menampilkan batasan pulau untuk pemain + calc: + parameters: "" + description: menghitung ulang batasan pulau untuk pemain + finished: "&aPenghitungan ulang pulau selesai tanpa masalah!" + offset: + unknown: "&c Material atau entitas tidak dikenal [name]." + main: + description: memungkinkan untuk mengelola batas offset untuk material dan + entitas + set: + parameters: " " + description: menetapkan offset baru untuk batasan material atau entitas + success: "&a Batas offset untuk [name] ditetapkan ke [number]." + same: "&a Batas offset untuk [name] ditetapkan ke [number]." + add: + parameters: " " + description: menambahkan offset untuk batasan material atau entitas + success: "&a Batas offset untuk [name] ditingkatkan hingga [number]." + remove: + parameters: " " + description: mengurangi offset untuk batas material atau entitas + success: "&a Batas offset untuk [name] dikurangi hingga [number]." + reset: + parameters: " " + description: menghapus offset untuk material atau entitas + success: "&a Batas offset untuk [name] ditetapkan ke 0." + view: + parameters: " " + description: menampilkan offset untuk material atau entitas + message: "&a [name] offset diatur ke [number]." +island: + limits: + description: menampilkan batasan pulau kamu + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Tidak ada batasan yang ditetapkan di dunia ini" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Pulau itu tidak memiliki pemilik" + not-on-island: "&c Lokasi ini tidak memiliki batasan yang ditetapkan." + recount: + description: menghitung ulang batasan untuk pulau kamu + now-recounting: "&b Menghitung ulang. Membutuhkan waktu beberapa saat, silahkan + tunggu..." + in-progress: "&c Perhitungan pulau sedang diproses. Silahkan tunggu..." + time-out: "&c Gagal menghitung ulang. Apakah pulau terlalu besar?" diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 2aeacad..53fb2ca 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] limitato a [number]!" -entity-limits: - hit-limit: "&c[entity] la generazione è limitata a [number]!" -limits: - panel-title: Limiti dell'isola -admin: - limits: - main: - parameters: "" - description: mostra i limiti dell'isola per il giocatore - calc: - parameters: "" - description: ricalcola i limiti dell'isola per il giocatore - finished: "&a Il ricalcolo dell'isola è stato completato con successo!" - offset: - unknown: "&c Materiale o entità sconosciuta [name]." - main: - description: consente di gestire gli offset dei limiti per materiali ed entità - set: - parameters: " " - description: imposta un nuovo offset per il limite di materiale o entità - success: "&a Il limite di offset per [name] è impostato su [number]." - same: "&c Il limite di offset per [name] è già [number]." - add: - parameters: " " - description: aggiunge offset per limite di materiale o entità - success: "&a Il limite di offset per [name] è aumentato fino a [number]." - remove: - parameters: " " - description: riduce l'offset per il limite di materiale o entità - success: "&a Il limite di offset per [name] è ridotto fino a [number]." - reset: - parameters: " " - description: rimuove l'offset per materiale o entità - success: "&a Il limite di offset per [name] è impostato su 0." - view: - parameters: " " - description: visualizza l'offset per materiale o entità - message: "&a [name] offset è impostato su [number]." -island: - limits: - description: mostra i limiti della tua isola - max-color: "&C" - regular-color: "&UN" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Non ci sono limiti in questo mondo" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Quell'isola non ha proprietario" - not-on-island: "&c Questa posizione non ha limiti impostati." - recount: - description: racconta i limiti per la tua isola - now-recounting: "&b Ora sto raccontando. Potrebbe volerci un po', attendi..." - in-progress: "&c Il conteggio dell'isola è in corso. Attendi..." - time-out: "&c Time out quando si racconta. L'isola è davvero grande?" +--- +block-limits: + hit-limit: "&c[material] limitato a [number]!" +entity-limits: + hit-limit: "&c[entity] la generazione è limitata a [number]!" +limits: + panel-title: Limiti dell'isola +admin: + limits: + main: + parameters: "" + description: mostra i limiti dell'isola per il giocatore + calc: + parameters: "" + description: ricalcola i limiti dell'isola per il giocatore + finished: "&a Il ricalcolo dell'isola è stato completato con successo!" + offset: + unknown: "&c Materiale o entità sconosciuta [name]." + main: + description: consente di gestire gli offset dei limiti per materiali ed entità + set: + parameters: " " + description: imposta un nuovo offset per il limite di materiale o entità + success: "&a Il limite di offset per [name] è impostato su [number]." + same: "&c Il limite di offset per [name] è già [number]." + add: + parameters: " " + description: aggiunge offset per limite di materiale o entità + success: "&a Il limite di offset per [name] è aumentato fino a [number]." + remove: + parameters: " " + description: riduce l'offset per il limite di materiale o entità + success: "&a Il limite di offset per [name] è ridotto fino a [number]." + reset: + parameters: " " + description: rimuove l'offset per materiale o entità + success: "&a Il limite di offset per [name] è impostato su 0." + view: + parameters: " " + description: visualizza l'offset per materiale o entità + message: "&a [name] offset è impostato su [number]." +island: + limits: + description: mostra i limiti della tua isola + max-color: "&C" + regular-color: "&UN" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Non ci sono limiti in questo mondo" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Quell'isola non ha proprietario" + not-on-island: "&c Questa posizione non ha limiti impostati." + recount: + description: racconta i limiti per la tua isola + now-recounting: "&b Ora sto raccontando. Potrebbe volerci un po', attendi..." + in-progress: "&c Il conteggio dell'isola è in corso. Attendi..." + time-out: "&c Time out quando si racconta. L'isola è davvero grande?" diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 9f2aa24..2b73b34 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -1,62 +1,62 @@ -block-limits: - hit-limit: '&c[material]は[number]に制限されています!' -entity-limits: - hit-limit: '&c[entity]の生成は[number]に制限されています!' -limits: - panel-title: 島の制限 -admin: - limits: - main: - parameters: <プレイヤー> - description: プレイヤーの島の制限を表示する - calc: - parameters: <プレイヤー> - description: プレイヤーの島の制限を再計算します - finished: '&a 島の再計算が正常に完了しました!' - offset: - unknown: '&c 不明な素材または実体 [name]。' - main: - description: 材料とエンティティの制限オフセットを管理できます - set: - parameters: <プレイヤー> <マテリアル|エンティティ> <番号> - description: 材料またはエンティティの制限の新しいオフセットを設定します - success: '&a [name] の制限オフセットが [number] に設定されています。' - same: '&c [name] の制限オフセットはすでに [number] です。' - add: - parameters: <プレイヤー> <マテリアル|エンティティ> <番号> - description: 材料またはエンティティの制限のオフセットを追加します - success: '&a [name] の制限オフセットが [number] まで増加されます。' - remove: - parameters: <プレイヤー> <マテリアル|エンティティ> <番号> - description: 材料またはエンティティの制限のオフセットを削減します - success: '&a [name] の制限オフセットが [number] まで削減されます。' - reset: - parameters: <プレイヤー> <マテリアル|エンティティ> - description: マテリアルまたはエンティティのオフセットを削除します - success: '&a [name] の制限オフセットが 0 に設定されています。' - view: - parameters: <プレイヤー> <マテリアル|エンティティ> - description: 材料またはエンティティのオフセットを表示します - message: '&a [name] オフセットが [number] に設定されています。' -island: - limits: - description: 島の限界を示す - block-limit-syntax: '[number]/[limit]' - no-limits: この世に限界はない - panel: - title-syntax: '[title] [sort]' - entity-group-name-syntax: '[name]' - entity-name-syntax: '[name]' - block-name-syntax: '[name]' - A2Z: a > z - Z2A: z > a - errors: - no-owner: その島には所有者がいない - not-on-island: '&c この場所には制限が設定されていません。' - recount: - description: あなたの島の限界を語る - now-recounting: '&b 今、話を戻します。これにはしばらく時間がかかるかもしれませんので、お待ちください...' - in-progress: '&c 島の回復作業が進行中です。お待ちください...' - time-out: '&c もう一度話すとタイムアウトになります。島は本当に大きいですか?' - max-color: '&c' - regular-color: '&a' +block-limits: + hit-limit: '&c[material]は[number]に制限されています!' +entity-limits: + hit-limit: '&c[entity]の生成は[number]に制限されています!' +limits: + panel-title: 島の制限 +admin: + limits: + main: + parameters: <プレイヤー> + description: プレイヤーの島の制限を表示する + calc: + parameters: <プレイヤー> + description: プレイヤーの島の制限を再計算します + finished: '&a 島の再計算が正常に完了しました!' + offset: + unknown: '&c 不明な素材または実体 [name]。' + main: + description: 材料とエンティティの制限オフセットを管理できます + set: + parameters: <プレイヤー> <マテリアル|エンティティ> <番号> + description: 材料またはエンティティの制限の新しいオフセットを設定します + success: '&a [name] の制限オフセットが [number] に設定されています。' + same: '&c [name] の制限オフセットはすでに [number] です。' + add: + parameters: <プレイヤー> <マテリアル|エンティティ> <番号> + description: 材料またはエンティティの制限のオフセットを追加します + success: '&a [name] の制限オフセットが [number] まで増加されます。' + remove: + parameters: <プレイヤー> <マテリアル|エンティティ> <番号> + description: 材料またはエンティティの制限のオフセットを削減します + success: '&a [name] の制限オフセットが [number] まで削減されます。' + reset: + parameters: <プレイヤー> <マテリアル|エンティティ> + description: マテリアルまたはエンティティのオフセットを削除します + success: '&a [name] の制限オフセットが 0 に設定されています。' + view: + parameters: <プレイヤー> <マテリアル|エンティティ> + description: 材料またはエンティティのオフセットを表示します + message: '&a [name] オフセットが [number] に設定されています。' +island: + limits: + description: 島の限界を示す + block-limit-syntax: '[number]/[limit]' + no-limits: この世に限界はない + panel: + title-syntax: '[title] [sort]' + entity-group-name-syntax: '[name]' + entity-name-syntax: '[name]' + block-name-syntax: '[name]' + A2Z: a > z + Z2A: z > a + errors: + no-owner: その島には所有者がいない + not-on-island: '&c この場所には制限が設定されていません。' + recount: + description: あなたの島の限界を語る + now-recounting: '&b 今、話を戻します。これにはしばらく時間がかかるかもしれませんので、お待ちください...' + in-progress: '&c 島の回復作業が進行中です。お待ちください...' + time-out: '&c もう一度話すとタイムアウトになります。島は本当に大きいですか?' + max-color: '&c' + regular-color: '&a' diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml index e68cf59..ae33d07 100644 --- a/src/main/resources/locales/ko.yml +++ b/src/main/resources/locales/ko.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material]는 [number]개로 제한됩니다!" -entity-limits: - hit-limit: "&c[entity]는 [number]개로 제한됩니다!" -limits: - panel-title: 섬의 경계 -admin: - limits: - main: - parameters: "<플레이어>" - description: 플레이어에게 섬의 한계를 보여주세요 - calc: - parameters: "<플레이어>" - description: 플레이어의 섬 한계를 다시 계산합니다 - finished: "&a 섬 재계산이 성공적으로 완료되었습니다!" - offset: - unknown: "&c 알 수 없는 자료 또는 엔터티 [name]." - main: - description: 재료 및 엔티티에 대한 제한 오프셋을 관리할 수 있습니다. - set: - parameters: "<플레이어> <소재|엔티티> <숫자>" - description: 재료 또는 엔티티 제한에 대한 새로운 오프셋을 설정합니다. - success: "&a [name]에 대한 제한 오프셋이 [number]로 설정되었습니다." - same: "&c [name]에 대한 제한 오프셋은 이미 [number]입니다." - add: - parameters: "<플레이어> <소재|엔티티> <숫자>" - description: 재료 또는 엔티티 제한에 대한 오프셋을 추가합니다. - success: "&a [name]에 대한 오프셋 제한이 [number]까지 증가합니다." - remove: - parameters: "<플레이어> <소재|엔티티> <숫자>" - description: 재료 또는 엔티티 제한에 대한 오프셋을 줄입니다. - success: "&a [name]에 대한 오프셋 제한은 [number]까지 감소합니다." - reset: - parameters: "<플레이어> <소재|엔티티>" - description: 재료 또는 엔티티에 대한 오프셋을 제거합니다. - success: "&a [name]에 대한 제한 오프셋이 0으로 설정되었습니다." - view: - parameters: "<플레이어> <소재|엔티티>" - description: 재료 또는 엔티티에 대한 오프셋을 표시합니다. - message: "&a [name] 오프셋이 [number]로 설정되었습니다." -island: - limits: - description: 섬의 경계를 보여주세요 - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c 이 세상에는 제한이 없습니다" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: 가 > 지 - Z2A: 지 > 아 - errors: - no-owner: "&c 그 섬에는 주인이 없다" - not-on-island: "&c 이 위치에는 제한이 설정되어 있지 않습니다." - recount: - description: 당신의 섬에 대한 한계를 다시 계산합니다 - now-recounting: "&b 지금 다시 계산 중입니다. 시간이 좀 걸릴 수 있으니 기다려 주세요..." - in-progress: "&c 섬 복구가 진행 중입니다. 잠시만 기다려 주세요..." - time-out: "&c 계산할 때 시간 초과. 섬이 정말 큰가요?" +--- +block-limits: + hit-limit: "&c[material]는 [number]개로 제한됩니다!" +entity-limits: + hit-limit: "&c[entity]는 [number]개로 제한됩니다!" +limits: + panel-title: 섬의 경계 +admin: + limits: + main: + parameters: "<플레이어>" + description: 플레이어에게 섬의 한계를 보여주세요 + calc: + parameters: "<플레이어>" + description: 플레이어의 섬 한계를 다시 계산합니다 + finished: "&a 섬 재계산이 성공적으로 완료되었습니다!" + offset: + unknown: "&c 알 수 없는 자료 또는 엔터티 [name]." + main: + description: 재료 및 엔티티에 대한 제한 오프셋을 관리할 수 있습니다. + set: + parameters: "<플레이어> <소재|엔티티> <숫자>" + description: 재료 또는 엔티티 제한에 대한 새로운 오프셋을 설정합니다. + success: "&a [name]에 대한 제한 오프셋이 [number]로 설정되었습니다." + same: "&c [name]에 대한 제한 오프셋은 이미 [number]입니다." + add: + parameters: "<플레이어> <소재|엔티티> <숫자>" + description: 재료 또는 엔티티 제한에 대한 오프셋을 추가합니다. + success: "&a [name]에 대한 오프셋 제한이 [number]까지 증가합니다." + remove: + parameters: "<플레이어> <소재|엔티티> <숫자>" + description: 재료 또는 엔티티 제한에 대한 오프셋을 줄입니다. + success: "&a [name]에 대한 오프셋 제한은 [number]까지 감소합니다." + reset: + parameters: "<플레이어> <소재|엔티티>" + description: 재료 또는 엔티티에 대한 오프셋을 제거합니다. + success: "&a [name]에 대한 제한 오프셋이 0으로 설정되었습니다." + view: + parameters: "<플레이어> <소재|엔티티>" + description: 재료 또는 엔티티에 대한 오프셋을 표시합니다. + message: "&a [name] 오프셋이 [number]로 설정되었습니다." +island: + limits: + description: 섬의 경계를 보여주세요 + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c 이 세상에는 제한이 없습니다" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: 가 > 지 + Z2A: 지 > 아 + errors: + no-owner: "&c 그 섬에는 주인이 없다" + not-on-island: "&c 이 위치에는 제한이 설정되어 있지 않습니다." + recount: + description: 당신의 섬에 대한 한계를 다시 계산합니다 + now-recounting: "&b 지금 다시 계산 중입니다. 시간이 좀 걸릴 수 있으니 기다려 주세요..." + in-progress: "&c 섬 복구가 진행 중입니다. 잠시만 기다려 주세요..." + time-out: "&c 계산할 때 시간 초과. 섬이 정말 큰가요?" diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml index a0da1a6..2881f12 100644 --- a/src/main/resources/locales/lv.yml +++ b/src/main/resources/locales/lv.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] ierobežots līdz [number]!" -entity-limits: - hit-limit: "&c[entity] rašanās ierobežots līdz [number]!" -limits: - panel-title: Salas ierobežojumi -admin: - limits: - main: - parameters: "" - description: rāda salas ierobežojumus priekš spēlētāja - calc: - parameters: "" - description: pārrēķinā salas ierobežojumus priekš spēlētāja - finished: "&aSalas ierobežojumu pārrēķināšana pabeigta!" - offset: - unknown: un c Nezināms materiāls vai vienība [name]. - main: - description: ļauj pārvaldīt limitu kompensācijas materiāliem un entītijām - set: - parameters: " " - description: nosaka jaunu nobīdi materiāla vai entītijas ierobežojumam - success: "&a Ierobežojuma nobīde [name] ir iestatīta uz [number]." - same: "&c [name] limita nobīde jau ir [number]." - add: - parameters: " " - description: pievieno nobīdi materiāla vai entītijas ierobežojumam - success: "&a Ierobežojuma nobīde [name] tiek palielināta līdz [number]." - remove: - parameters: " " - description: samazina materiāla vai entītijas limita kompensāciju - success: "&a Ierobežojuma nobīde [name] tiek samazināta līdz [number]." - reset: - parameters: " " - description: noņem materiāla vai entītijas nobīdi - success: "&a Ierobežojuma nobīde [name] ir iestatīta uz 0." - view: - parameters: " " - description: parāda nobīdi materiālam vai vienībai - message: "&a [name] nobīde ir iestatīta uz [number]." -island: - limits: - description: rāda tavas salas ierobežojumus - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Šajā pasaulē nav noteikti ierobežojumi" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Tai salai nav saimnieka" - not-on-island: "&c Šai vietai nav noteikti ierobežojumi." - recount: - description: pārrēķina ierobežojumus tavai salai - now-recounting: "&b Tagad atstāsta. Tas var aizņemt kādu laiku, lūdzu, uzgaidiet..." - in-progress: "&c Notiek salas atjaunošana. Lūdzu, uzgaidiet..." - time-out: "&c Noildze, pārskaitot. Vai tiešām sala ir liela?" +--- +block-limits: + hit-limit: "&c[material] ierobežots līdz [number]!" +entity-limits: + hit-limit: "&c[entity] rašanās ierobežots līdz [number]!" +limits: + panel-title: Salas ierobežojumi +admin: + limits: + main: + parameters: "" + description: rāda salas ierobežojumus priekš spēlētāja + calc: + parameters: "" + description: pārrēķinā salas ierobežojumus priekš spēlētāja + finished: "&aSalas ierobežojumu pārrēķināšana pabeigta!" + offset: + unknown: un c Nezināms materiāls vai vienība [name]. + main: + description: ļauj pārvaldīt limitu kompensācijas materiāliem un entītijām + set: + parameters: " " + description: nosaka jaunu nobīdi materiāla vai entītijas ierobežojumam + success: "&a Ierobežojuma nobīde [name] ir iestatīta uz [number]." + same: "&c [name] limita nobīde jau ir [number]." + add: + parameters: " " + description: pievieno nobīdi materiāla vai entītijas ierobežojumam + success: "&a Ierobežojuma nobīde [name] tiek palielināta līdz [number]." + remove: + parameters: " " + description: samazina materiāla vai entītijas limita kompensāciju + success: "&a Ierobežojuma nobīde [name] tiek samazināta līdz [number]." + reset: + parameters: " " + description: noņem materiāla vai entītijas nobīdi + success: "&a Ierobežojuma nobīde [name] ir iestatīta uz 0." + view: + parameters: " " + description: parāda nobīdi materiālam vai vienībai + message: "&a [name] nobīde ir iestatīta uz [number]." +island: + limits: + description: rāda tavas salas ierobežojumus + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Šajā pasaulē nav noteikti ierobežojumi" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Tai salai nav saimnieka" + not-on-island: "&c Šai vietai nav noteikti ierobežojumi." + recount: + description: pārrēķina ierobežojumus tavai salai + now-recounting: "&b Tagad atstāsta. Tas var aizņemt kādu laiku, lūdzu, uzgaidiet..." + in-progress: "&c Notiek salas atjaunošana. Lūdzu, uzgaidiet..." + time-out: "&c Noildze, pārskaitot. Vai tiešām sala ir liela?" diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml index 34159f5..bdde128 100644 --- a/src/main/resources/locales/nl.yml +++ b/src/main/resources/locales/nl.yml @@ -1,65 +1,65 @@ ---- -block-limits: - hit-limit: "&c[material] beperkt tot [number]!" -entity-limits: - hit-limit: "&c[entity] spawning beperkt tot [number]!" -limits: - panel-title: Eilandgrenzen -admin: - limits: - main: - parameters: "" - description: toon de eilandgrenzen voor de speler - calc: - parameters: "" - description: herbereken de eilandlimieten voor de speler - finished: "&a Eiland herberekening succesvol afgerond!" - offset: - unknown: "&c Onbekend materiaal of entiteit [name]." - main: - description: maakt het mogelijk om limietoffsets voor materialen en entiteiten - te beheren - set: - parameters: " " - description: stelt nieuwe offset in voor materiaal- of entiteitslimiet - success: "&a Limietoffset voor [name] is ingesteld op [number]." - same: "&c Limietoffset voor [name] is al [number]." - add: - parameters: " " - description: voegt offset toe voor materiaal- of entiteitslimiet - success: "&a Limietoffset voor [name] is verhoogd tot [number]." - remove: - parameters: " " - description: vermindert offset voor materiaal- of entiteitslimiet - success: "&a Limietoffset voor [name] is verlaagd tot [number]." - reset: - parameters: " " - description: verwijdert offset voor materiaal of entiteit - success: "&a Limietoffset voor [name] is ingesteld op 0." - view: - parameters: " " - description: geeft offset weer voor materiaal of entiteit - message: "&a [name] offset is ingesteld op [number]." -island: - limits: - description: toon uw eilandgrenzen - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Er zijn geen grenzen gesteld in deze wereld" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Dat eiland heeft geen eigenaar" - not-on-island: "&c Voor deze locatie zijn geen limieten ingesteld." - recount: - description: vertelt over de grenzen van uw eiland - now-recounting: "&b Nu aan het navertellen. Dit kan even duren, even geduld - aub..." - in-progress: "&c Eilandrecovery is bezig. Even geduld..." - time-out: "&c Time-out bij het navertellen. Is het eiland echt groot?" +--- +block-limits: + hit-limit: "&c[material] beperkt tot [number]!" +entity-limits: + hit-limit: "&c[entity] spawning beperkt tot [number]!" +limits: + panel-title: Eilandgrenzen +admin: + limits: + main: + parameters: "" + description: toon de eilandgrenzen voor de speler + calc: + parameters: "" + description: herbereken de eilandlimieten voor de speler + finished: "&a Eiland herberekening succesvol afgerond!" + offset: + unknown: "&c Onbekend materiaal of entiteit [name]." + main: + description: maakt het mogelijk om limietoffsets voor materialen en entiteiten + te beheren + set: + parameters: " " + description: stelt nieuwe offset in voor materiaal- of entiteitslimiet + success: "&a Limietoffset voor [name] is ingesteld op [number]." + same: "&c Limietoffset voor [name] is al [number]." + add: + parameters: " " + description: voegt offset toe voor materiaal- of entiteitslimiet + success: "&a Limietoffset voor [name] is verhoogd tot [number]." + remove: + parameters: " " + description: vermindert offset voor materiaal- of entiteitslimiet + success: "&a Limietoffset voor [name] is verlaagd tot [number]." + reset: + parameters: " " + description: verwijdert offset voor materiaal of entiteit + success: "&a Limietoffset voor [name] is ingesteld op 0." + view: + parameters: " " + description: geeft offset weer voor materiaal of entiteit + message: "&a [name] offset is ingesteld op [number]." +island: + limits: + description: toon uw eilandgrenzen + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Er zijn geen grenzen gesteld in deze wereld" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Dat eiland heeft geen eigenaar" + not-on-island: "&c Voor deze locatie zijn geen limieten ingesteld." + recount: + description: vertelt over de grenzen van uw eiland + now-recounting: "&b Nu aan het navertellen. Dit kan even duren, even geduld + aub..." + in-progress: "&c Eilandrecovery is bezig. Even geduld..." + time-out: "&c Time-out bij het navertellen. Is het eiland echt groot?" diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index eec6537..aa338d0 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -1,64 +1,64 @@ ---- -block-limits: - hit-limit: "&c[material] limitowany do [number]!" -entity-limits: - hit-limit: "&cSpawnowanie [entity] limitowane do [number]!" -limits: - panel-title: Limity wysp -admin: - limits: - main: - parameters: "" - description: pokazuje limity wysp gracza - calc: - parameters: "" - description: ponownie oblicza limity wyspy dla gracza - finished: "&aPrzeliczanie zakończone!" - offset: - unknown: "&c Nieznany materiał lub podmiot [name]." - main: - description: pozwala zarządzać limitów dla materiałów i podmiotów, - set: - parameters: " " - description: ustawia nową wartość dla limitu materiału lub encji - success: "&a Przesunięcie limitu dla [name] jest ustawione na [number]." - same: "&c Limit dla [name] jest aktualnie [number]." - add: - parameters: " " - description: dodaje przesunięcie dla limitu materiału lub potworów - success: "&a Przesunięcie limitu dla [name] jest zwiększone do [number]." - remove: - parameters: " " - description: zmniejsza offset dla limitu materiału lub podmiotu - success: "&a Limit dla bloku [name] \nzostał zmniejszony do [number]." - reset: - parameters: " " - description: usuwa wartość dla materiału lub istoty - success: "&a Limit dla [name] jest ustawione na 0." - view: - parameters: " " - description: displays offset for material or entity - message: "&a [name] wartość jest ustawiona na [number]." -island: - limits: - description: pokazuje limity twojej wyspy - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Na tym świecie nie ma żadnych ograniczeń" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Ta wyspa nie ma właściciela" - not-on-island: "&c Ta lokalizacja nie ma ustalonych ograniczeń." - recount: - description: określa limity dla twojej wyspy - now-recounting: "&b Teraz przeliczam. To może chwilę potrwać, proszę czekać..." - in-progress: "&c Trwa odzyskiwanie wyspy. Proszę czekać..." - time-out: "&c Przekroczono limit czasu podczas przeliczania. Czy wyspa jest - naprawdę duża?" +--- +block-limits: + hit-limit: "&c[material] limitowany do [number]!" +entity-limits: + hit-limit: "&cSpawnowanie [entity] limitowane do [number]!" +limits: + panel-title: Limity wysp +admin: + limits: + main: + parameters: "" + description: pokazuje limity wysp gracza + calc: + parameters: "" + description: ponownie oblicza limity wyspy dla gracza + finished: "&aPrzeliczanie zakończone!" + offset: + unknown: "&c Nieznany materiał lub podmiot [name]." + main: + description: pozwala zarządzać limitów dla materiałów i podmiotów, + set: + parameters: " " + description: ustawia nową wartość dla limitu materiału lub encji + success: "&a Przesunięcie limitu dla [name] jest ustawione na [number]." + same: "&c Limit dla [name] jest aktualnie [number]." + add: + parameters: " " + description: dodaje przesunięcie dla limitu materiału lub potworów + success: "&a Przesunięcie limitu dla [name] jest zwiększone do [number]." + remove: + parameters: " " + description: zmniejsza offset dla limitu materiału lub podmiotu + success: "&a Limit dla bloku [name] \nzostał zmniejszony do [number]." + reset: + parameters: " " + description: usuwa wartość dla materiału lub istoty + success: "&a Limit dla [name] jest ustawione na 0." + view: + parameters: " " + description: displays offset for material or entity + message: "&a [name] wartość jest ustawiona na [number]." +island: + limits: + description: pokazuje limity twojej wyspy + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Na tym świecie nie ma żadnych ograniczeń" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Ta wyspa nie ma właściciela" + not-on-island: "&c Ta lokalizacja nie ma ustalonych ograniczeń." + recount: + description: określa limity dla twojej wyspy + now-recounting: "&b Teraz przeliczam. To może chwilę potrwać, proszę czekać..." + in-progress: "&c Trwa odzyskiwanie wyspy. Proszę czekać..." + time-out: "&c Przekroczono limit czasu podczas przeliczania. Czy wyspa jest + naprawdę duża?" diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index 0470a20..3db51a5 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] limitado a [number]!" -entity-limits: - hit-limit: "&c[entity] gerando limitado a [number]!" -limits: - panel-title: Limites da ilha -admin: - limits: - main: - parameters: "" - description: mostrar os limites da ilha para o jogador - calc: - parameters: "" - description: recalcular os limites da ilha para o jogador - finished: "&a O recálculo da ilha foi concluído com sucesso!" - offset: - unknown: "&c Material ou entidade desconhecida [name]." - main: - description: permite gerenciar limites de deslocamento para materiais e entidades - set: - parameters: " " - description: define novo deslocamento para limite de material ou entidade - success: "&a O deslocamento de limite para [name] é definido como [number]." - same: "&c O deslocamento limite para [name] já é [number]." - add: - parameters: " " - description: adiciona deslocamento para limite de material ou entidade - success: "&a O deslocamento de limite para [name] é aumentado até [number]." - remove: - parameters: " " - description: reduz o deslocamento para o limite de material ou entidade - success: "&a O deslocamento de limite para [name] é reduzido até [number]." - reset: - parameters: " " - description: remove deslocamento para material ou entidade - success: "&a O deslocamento de limite para [name] é definido como 0." - view: - parameters: " " - description: exibe deslocamento para material ou entidade - message: "&a [name] deslocamento é definido como [number]." -island: - limits: - description: mostre os limites da sua ilha - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Não há limites neste mundo" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Aquela ilha não tem dono" - not-on-island: "&c Este local não possui limites definidos." - recount: - description: reconta limites para sua ilha - now-recounting: "&b Agora recontando. Isso pode demorar um pouco, aguarde..." - in-progress: "&c A recontagem da ilha está em andamento. Aguarde..." - time-out: "&c Tempo limite ao recontar. A ilha é realmente grande?" +--- +block-limits: + hit-limit: "&c[material] limitado a [number]!" +entity-limits: + hit-limit: "&c[entity] gerando limitado a [number]!" +limits: + panel-title: Limites da ilha +admin: + limits: + main: + parameters: "" + description: mostrar os limites da ilha para o jogador + calc: + parameters: "" + description: recalcular os limites da ilha para o jogador + finished: "&a O recálculo da ilha foi concluído com sucesso!" + offset: + unknown: "&c Material ou entidade desconhecida [name]." + main: + description: permite gerenciar limites de deslocamento para materiais e entidades + set: + parameters: " " + description: define novo deslocamento para limite de material ou entidade + success: "&a O deslocamento de limite para [name] é definido como [number]." + same: "&c O deslocamento limite para [name] já é [number]." + add: + parameters: " " + description: adiciona deslocamento para limite de material ou entidade + success: "&a O deslocamento de limite para [name] é aumentado até [number]." + remove: + parameters: " " + description: reduz o deslocamento para o limite de material ou entidade + success: "&a O deslocamento de limite para [name] é reduzido até [number]." + reset: + parameters: " " + description: remove deslocamento para material ou entidade + success: "&a O deslocamento de limite para [name] é definido como 0." + view: + parameters: " " + description: exibe deslocamento para material ou entidade + message: "&a [name] deslocamento é definido como [number]." +island: + limits: + description: mostre os limites da sua ilha + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Não há limites neste mundo" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Aquela ilha não tem dono" + not-on-island: "&c Este local não possui limites definidos." + recount: + description: reconta limites para sua ilha + now-recounting: "&b Agora recontando. Isso pode demorar um pouco, aguarde..." + in-progress: "&c A recontagem da ilha está em andamento. Aguarde..." + time-out: "&c Tempo limite ao recontar. A ilha é realmente grande?" diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml index 8a15672..76b0780 100644 --- a/src/main/resources/locales/ro.yml +++ b/src/main/resources/locales/ro.yml @@ -1,65 +1,65 @@ ---- -block-limits: - hit-limit: "&c[material] limitat la [number]!" -entity-limits: - hit-limit: "&c[entity] reproducere limitata la [number]!" -limits: - panel-title: Limitele insulei -admin: - limits: - main: - parameters: "" - description: Arata limitele insulei pentru jucator - calc: - parameters: "" - description: recalculeaza limitele insulei pentru jocator - finished: "&aRecalcularea insulei terminata cu succes!" - offset: - unknown: "&c Material sau entitate necunoscută [name]." - main: - description: permite gestionarea decalajelor de limite pentru materiale și - entități - set: - parameters: " " - description: stabilește o nouă compensare pentru limita de material sau entitate - success: "&a Decalajul limită pentru [name] este setat la [number]." - same: "&c Decalajul limită pentru [name] este deja [number]." - add: - parameters: " " - description: adaugă compensare pentru limita de material sau entitate - success: "&a Compensarea limită pentru [name] este mărită până la [number]." - remove: - parameters: " " - description: reduce compensarea pentru limita de material sau entitate - success: "&a Limita decalajului pentru [name] este redusă până la [number]." - reset: - parameters: " " - description: elimină decalajul pentru material sau entitate - success: "&a Decalajul limită pentru [name] este setat la 0." - view: - parameters: " " - description: afișează offset pentru material sau entitate - message: "&a [name] offset este setat la [number]." -island: - limits: - description: iti arata limitele insulei - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Nu există limite stabilite în această lume" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Insula aceea nu are proprietar" - not-on-island: "&c Această locație nu are limite setate." - recount: - description: renumara limitele insulei tale - now-recounting: "&b Acum povestind. Acest lucru ar putea dura ceva timp, vă - rugăm să așteptați..." - in-progress: "&c Recuperarea insulei este în curs. Va rugam asteptati..." - time-out: "&c Timpul expirat când relatați. Este insula cu adevărat mare?" +--- +block-limits: + hit-limit: "&c[material] limitat la [number]!" +entity-limits: + hit-limit: "&c[entity] reproducere limitata la [number]!" +limits: + panel-title: Limitele insulei +admin: + limits: + main: + parameters: "" + description: Arata limitele insulei pentru jucator + calc: + parameters: "" + description: recalculeaza limitele insulei pentru jocator + finished: "&aRecalcularea insulei terminata cu succes!" + offset: + unknown: "&c Material sau entitate necunoscută [name]." + main: + description: permite gestionarea decalajelor de limite pentru materiale și + entități + set: + parameters: " " + description: stabilește o nouă compensare pentru limita de material sau entitate + success: "&a Decalajul limită pentru [name] este setat la [number]." + same: "&c Decalajul limită pentru [name] este deja [number]." + add: + parameters: " " + description: adaugă compensare pentru limita de material sau entitate + success: "&a Compensarea limită pentru [name] este mărită până la [number]." + remove: + parameters: " " + description: reduce compensarea pentru limita de material sau entitate + success: "&a Limita decalajului pentru [name] este redusă până la [number]." + reset: + parameters: " " + description: elimină decalajul pentru material sau entitate + success: "&a Decalajul limită pentru [name] este setat la 0." + view: + parameters: " " + description: afișează offset pentru material sau entitate + message: "&a [name] offset este setat la [number]." +island: + limits: + description: iti arata limitele insulei + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Nu există limite stabilite în această lume" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Insula aceea nu are proprietar" + not-on-island: "&c Această locație nu are limite setate." + recount: + description: renumara limitele insulei tale + now-recounting: "&b Acum povestind. Acest lucru ar putea dura ceva timp, vă + rugăm să așteptați..." + in-progress: "&c Recuperarea insulei este în curs. Va rugam asteptati..." + time-out: "&c Timpul expirat când relatați. Este insula cu adevărat mare?" diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 9e1ae9b..e02eed1 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -1,65 +1,65 @@ ---- -block-limits: - hit-limit: "&e[material] &4eşyasından &5[number] &4koyabilirsin!" -entity-limits: - hit-limit: "&e[entity] &4varlığından &5[number] &4tane koyabilirsin!" -limits: - panel-title: "&eAda limiti" -admin: - limits: - main: - parameters: "" - description: Oyuncu için ada limitlerini göster. - calc: - parameters: "" - description: Oyuncu için ada limitlerini tekrar hesapla. - finished: "&aAda hesaplaması başarıyla yapıldı." - offset: - unknown: "&c Bilinmeyen malzeme veya varlık [name]." - main: - description: malzemeler ve varlıklar için limit ofsetlerini yönetmeye olanak - tanır - set: - parameters: " " - description: malzeme veya varlık sınırı için yeni ofset ayarlar - success: "&[name] için Limit ofseti [number] olarak ayarlandı." - same: "&c [name] için sınır ofseti zaten [number]'dır." - add: - parameters: " " - description: malzeme veya varlık sınırı için ofset ekler - success: "&[name] için Limit ofseti [number]'ya kadar artırıldı." - remove: - parameters: " " - description: malzeme veya varlık sınırı için ofseti azaltır - success: "&[name] için limit ofseti [number]'ya kadar azaltıldı." - reset: - parameters: " " - description: malzeme veya varlık için ofseti kaldırır - success: "&[name] için Limit ofseti 0 olarak ayarlandı." - view: - parameters: " " - description: malzeme veya varlık için ofseti görüntüler - message: "&a [name] ofseti [number] olarak ayarlandı." -island: - limits: - description: Ada limitlerini gör. - max-color: "&c" - regular-color: "&9" - block-limit-syntax: "&5[number]&7/&e[limit]" - no-limits: "&c Bu dünyada hiçbir sınır belirlenmedi" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c O adanın bir sahibi yok" - not-on-island: "&c Bu lokasyonun belirlenmiş bir sınırı yok." - recount: - description: Ada limitlerini tekrardan hesaplar. - now-recounting: "&b Şimdi anlatmaya başlıyorum. Biraz zaman alabilir, lütfen - bekleyin..." - in-progress: "&c Ada geri kazanımı devam ediyor. Lütfen bekleyin..." - time-out: "&c Anlatırken zaman aşımı. Ada gerçekten büyük mü?" +--- +block-limits: + hit-limit: "&e[material] &4eşyasından &5[number] &4koyabilirsin!" +entity-limits: + hit-limit: "&e[entity] &4varlığından &5[number] &4tane koyabilirsin!" +limits: + panel-title: "&eAda limiti" +admin: + limits: + main: + parameters: "" + description: Oyuncu için ada limitlerini göster. + calc: + parameters: "" + description: Oyuncu için ada limitlerini tekrar hesapla. + finished: "&aAda hesaplaması başarıyla yapıldı." + offset: + unknown: "&c Bilinmeyen malzeme veya varlık [name]." + main: + description: malzemeler ve varlıklar için limit ofsetlerini yönetmeye olanak + tanır + set: + parameters: " " + description: malzeme veya varlık sınırı için yeni ofset ayarlar + success: "&[name] için Limit ofseti [number] olarak ayarlandı." + same: "&c [name] için sınır ofseti zaten [number]'dır." + add: + parameters: " " + description: malzeme veya varlık sınırı için ofset ekler + success: "&[name] için Limit ofseti [number]'ya kadar artırıldı." + remove: + parameters: " " + description: malzeme veya varlık sınırı için ofseti azaltır + success: "&[name] için limit ofseti [number]'ya kadar azaltıldı." + reset: + parameters: " " + description: malzeme veya varlık için ofseti kaldırır + success: "&[name] için Limit ofseti 0 olarak ayarlandı." + view: + parameters: " " + description: malzeme veya varlık için ofseti görüntüler + message: "&a [name] ofseti [number] olarak ayarlandı." +island: + limits: + description: Ada limitlerini gör. + max-color: "&c" + regular-color: "&9" + block-limit-syntax: "&5[number]&7/&e[limit]" + no-limits: "&c Bu dünyada hiçbir sınır belirlenmedi" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c O adanın bir sahibi yok" + not-on-island: "&c Bu lokasyonun belirlenmiş bir sınırı yok." + recount: + description: Ada limitlerini tekrardan hesaplar. + now-recounting: "&b Şimdi anlatmaya başlıyorum. Biraz zaman alabilir, lütfen + bekleyin..." + in-progress: "&c Ada geri kazanımı devam ediyor. Lütfen bekleyin..." + time-out: "&c Anlatırken zaman aşımı. Ada gerçekten büyük mü?" diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index 507459b..abffe7e 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] обмежено [number]!" -entity-limits: - hit-limit: "&c[entity] породження обмежено до [number]!" -limits: - panel-title: Межі острова -admin: - limits: - main: - parameters: "<гравець>" - description: показати обмеження острова для гравця - calc: - parameters: "<гравець>" - description: перерахувати обмеження острова для гравця - finished: Перерахунок острова успішно завершено! - offset: - unknown: "&c Невідомий матеріал або сутність [name]." - main: - description: дозволяє керувати зсувами лімітів для матеріалів і сутностей - set: - parameters: "<гравець> <матеріал|сутність> <номер>" - description: встановлює нове зміщення для обмеження матеріалу або сутності - success: "&a Лімітне зміщення для [name] встановлено на [number]." - same: "&c Обмежене зміщення для [name] вже [number]." - add: - parameters: "<гравець> <матеріал|сутність> <номер>" - description: додає зсув для обмеження матеріалу або сутності - success: "&a Лімітне зміщення для [name] збільшується до [number]." - remove: - parameters: "<гравець> <матеріал|сутність> <номер>" - description: зменшує зсув для ліміту матеріалу або сутності - success: "&a Лімітне зміщення для [name] зменшується до [number]." - reset: - parameters: "<гравець> <матеріал|сутність>" - description: видаляє зсув для матеріалу або сутності - success: "&a Лімітне зміщення для [name] встановлено на 0." - view: - parameters: "<гравець> <матеріал|сутність>" - description: відображає зсув для матеріалу або сутності - message: "&зміщення [name] встановлено на [number]." -island: - limits: - description: покажіть межі свого острова - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c У цьому світі немає обмежень" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Цей острів не має власника" - not-on-island: "&c Це місце не має обмежень." - recount: - description: перераховує ліміти для вашого острова - now-recounting: "&b Тепер перераховую. Це може зайняти деякий час, зачекайте..." - in-progress: "&c Перерахування острова триває. Будь ласка, зачекайте..." - time-out: "&c Тайм-аут під час перерахунку. Чи справді острів великий?" +--- +block-limits: + hit-limit: "&c[material] обмежено [number]!" +entity-limits: + hit-limit: "&c[entity] породження обмежено до [number]!" +limits: + panel-title: Межі острова +admin: + limits: + main: + parameters: "<гравець>" + description: показати обмеження острова для гравця + calc: + parameters: "<гравець>" + description: перерахувати обмеження острова для гравця + finished: Перерахунок острова успішно завершено! + offset: + unknown: "&c Невідомий матеріал або сутність [name]." + main: + description: дозволяє керувати зсувами лімітів для матеріалів і сутностей + set: + parameters: "<гравець> <матеріал|сутність> <номер>" + description: встановлює нове зміщення для обмеження матеріалу або сутності + success: "&a Лімітне зміщення для [name] встановлено на [number]." + same: "&c Обмежене зміщення для [name] вже [number]." + add: + parameters: "<гравець> <матеріал|сутність> <номер>" + description: додає зсув для обмеження матеріалу або сутності + success: "&a Лімітне зміщення для [name] збільшується до [number]." + remove: + parameters: "<гравець> <матеріал|сутність> <номер>" + description: зменшує зсув для ліміту матеріалу або сутності + success: "&a Лімітне зміщення для [name] зменшується до [number]." + reset: + parameters: "<гравець> <матеріал|сутність>" + description: видаляє зсув для матеріалу або сутності + success: "&a Лімітне зміщення для [name] встановлено на 0." + view: + parameters: "<гравець> <матеріал|сутність>" + description: відображає зсув для матеріалу або сутності + message: "&зміщення [name] встановлено на [number]." +island: + limits: + description: покажіть межі свого острова + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c У цьому світі немає обмежень" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Цей острів не має власника" + not-on-island: "&c Це місце не має обмежень." + recount: + description: перераховує ліміти для вашого острова + now-recounting: "&b Тепер перераховую. Це може зайняти деякий час, зачекайте..." + in-progress: "&c Перерахування острова триває. Будь ласка, зачекайте..." + time-out: "&c Тайм-аут під час перерахунку. Чи справді острів великий?" diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 4ddf8cc..4a199ef 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] bị giới hạn trong [number] khối!" -entity-limits: - hit-limit: "&c[entity] bị giới hạn trong [number] thực thể!" -limits: - panel-title: Giới hạn đảo -admin: - limits: - main: - parameters: "" - description: xem giới hạn đảo của người chơi - calc: - parameters: "" - description: tính toán lại giới hạn đảo của người chơi - finished: "&aTính toán đã hoàn thành!" - offset: - unknown: "&c Tài liệu hoặc thực thể không xác định [name]." - main: - description: cho phép quản lý các giới hạn bù trừ cho vật liệu và thực thể - set: - parameters: " " - description: thiết lập độ lệch mới cho giới hạn vật liệu hoặc thực thể - success: "&a Độ lệch giới hạn cho [name] được đặt thành [number]." - same: "&c Độ lệch giới hạn cho [name] đã là [number]." - add: - parameters: " " - description: thêm độ lệch cho giới hạn vật liệu hoặc thực thể - success: "&a Độ lệch giới hạn cho [name] được tăng lên cho đến [number]." - remove: - parameters: " " - description: giảm độ lệch cho giới hạn vật liệu hoặc thực thể - success: "&a Độ lệch giới hạn cho [name] được giảm xuống đến [number]." - reset: - parameters: " " - description: xóa phần bù cho vật liệu hoặc thực thể - success: "&a Độ lệch giới hạn cho [name] được đặt thành 0." - view: - parameters: " " - description: hiển thị bù trừ cho vật liệu hoặc thực thể - message: "&a [name] bù trừ được đặt thành [number]." -island: - limits: - description: xem giới hạn đảo của bạn - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c Không có giới hạn nào được đặt ra trên thế giới này" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c Hòn đảo đó không có chủ sở hữu" - not-on-island: "&c Vị trí này không có giới hạn nào được đặt ra." - recount: - description: tính toán lại giới hạn đảo của bạn - now-recounting: "&b Đang đếm lại. Việc này có thể mất một lúc, vui lòng đợi..." - in-progress: "&c Đảo đang được thu hồi. Vui lòng đợi..." - time-out: "&c Hết giờ khi kể lại. Hòn đảo có thực sự lớn không?" +--- +block-limits: + hit-limit: "&c[material] bị giới hạn trong [number] khối!" +entity-limits: + hit-limit: "&c[entity] bị giới hạn trong [number] thực thể!" +limits: + panel-title: Giới hạn đảo +admin: + limits: + main: + parameters: "" + description: xem giới hạn đảo của người chơi + calc: + parameters: "" + description: tính toán lại giới hạn đảo của người chơi + finished: "&aTính toán đã hoàn thành!" + offset: + unknown: "&c Tài liệu hoặc thực thể không xác định [name]." + main: + description: cho phép quản lý các giới hạn bù trừ cho vật liệu và thực thể + set: + parameters: " " + description: thiết lập độ lệch mới cho giới hạn vật liệu hoặc thực thể + success: "&a Độ lệch giới hạn cho [name] được đặt thành [number]." + same: "&c Độ lệch giới hạn cho [name] đã là [number]." + add: + parameters: " " + description: thêm độ lệch cho giới hạn vật liệu hoặc thực thể + success: "&a Độ lệch giới hạn cho [name] được tăng lên cho đến [number]." + remove: + parameters: " " + description: giảm độ lệch cho giới hạn vật liệu hoặc thực thể + success: "&a Độ lệch giới hạn cho [name] được giảm xuống đến [number]." + reset: + parameters: " " + description: xóa phần bù cho vật liệu hoặc thực thể + success: "&a Độ lệch giới hạn cho [name] được đặt thành 0." + view: + parameters: " " + description: hiển thị bù trừ cho vật liệu hoặc thực thể + message: "&a [name] bù trừ được đặt thành [number]." +island: + limits: + description: xem giới hạn đảo của bạn + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c Không có giới hạn nào được đặt ra trên thế giới này" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c Hòn đảo đó không có chủ sở hữu" + not-on-island: "&c Vị trí này không có giới hạn nào được đặt ra." + recount: + description: tính toán lại giới hạn đảo của bạn + now-recounting: "&b Đang đếm lại. Việc này có thể mất một lúc, vui lòng đợi..." + in-progress: "&c Đảo đang được thu hồi. Vui lòng đợi..." + time-out: "&c Hết giờ khi kể lại. Hòn đảo có thực sự lớn không?" diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 6583e3e..2a816bb 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material] 已限制到 [number]!" -entity-limits: - hit-limit: "&c[entity] 生成已限制到 [number]!" -limits: - panel-title: 岛屿限制 -admin: - limits: - main: - parameters: "<玩家>" - description: 显示玩家的限制 - calc: - parameters: "<玩家>" - description: 重新计算玩家的岛屿限制 - finished: "&a岛屿重计算已完成!" - offset: - unknown: "&c 未知物质或实体 [name]。" - main: - description: 允许管理材料和实体的限制偏移 - set: - parameters: "<玩家> <材质|实体> <数字>" - description: 为材料或实体限制设置新的偏移 - success: "&a 将 [name] 的限制偏移量设置为 [number]。" - same: "&c [name] 的限制偏移量已经为 [number]。" - add: - parameters: "<玩家> <材质|实体> <数字>" - description: 添加材料或实体限制的偏移 - success: "&a [name] 的限制偏移量增加至 [number]。" - remove: - parameters: "<玩家> <材质|实体> <数字>" - description: 减少材料或实体限制的偏移量 - success: "&a [name] 的限制偏移量减少至 [number]。" - reset: - parameters: "<玩家> <材质|实体>" - description: 删除材料或实体的偏移 - success: "&a [name] 的限制偏移量设置为 0。" - view: - parameters: "<玩家> <材质|实体>" - description: 显示材料或实体的偏移 - message: "&a [name] offset 设置为 [number]。" -island: - limits: - description: 显示您的岛屿限制 - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c 这世上没有限制" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c 那个岛没有主人" - not-on-island: "&c 此位置未设置限制。" - recount: - description: 重新计数岛屿限制 - now-recounting: "&b 开始重新计算. 可能需要一定的时间, 请稍等......" - in-progress: "&c 重新计算岛屿限制中. 请稍等......" - time-out: "&c 重新计算超时. 岛屿太大了吗?" +--- +block-limits: + hit-limit: "&c[material] 已限制到 [number]!" +entity-limits: + hit-limit: "&c[entity] 生成已限制到 [number]!" +limits: + panel-title: 岛屿限制 +admin: + limits: + main: + parameters: "<玩家>" + description: 显示玩家的限制 + calc: + parameters: "<玩家>" + description: 重新计算玩家的岛屿限制 + finished: "&a岛屿重计算已完成!" + offset: + unknown: "&c 未知物质或实体 [name]。" + main: + description: 允许管理材料和实体的限制偏移 + set: + parameters: "<玩家> <材质|实体> <数字>" + description: 为材料或实体限制设置新的偏移 + success: "&a 将 [name] 的限制偏移量设置为 [number]。" + same: "&c [name] 的限制偏移量已经为 [number]。" + add: + parameters: "<玩家> <材质|实体> <数字>" + description: 添加材料或实体限制的偏移 + success: "&a [name] 的限制偏移量增加至 [number]。" + remove: + parameters: "<玩家> <材质|实体> <数字>" + description: 减少材料或实体限制的偏移量 + success: "&a [name] 的限制偏移量减少至 [number]。" + reset: + parameters: "<玩家> <材质|实体>" + description: 删除材料或实体的偏移 + success: "&a [name] 的限制偏移量设置为 0。" + view: + parameters: "<玩家> <材质|实体>" + description: 显示材料或实体的偏移 + message: "&a [name] offset 设置为 [number]。" +island: + limits: + description: 显示您的岛屿限制 + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c 这世上没有限制" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c 那个岛没有主人" + not-on-island: "&c 此位置未设置限制。" + recount: + description: 重新计数岛屿限制 + now-recounting: "&b 开始重新计算. 可能需要一定的时间, 请稍等......" + in-progress: "&c 重新计算岛屿限制中. 请稍等......" + time-out: "&c 重新计算超时. 岛屿太大了吗?" diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 20dd3fb..cad8dca 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -1,63 +1,63 @@ ---- -block-limits: - hit-limit: "&c[material]僅限[number]!" -entity-limits: - hit-limit: "&c[entity] 產生僅限於 [number]!" -limits: - panel-title: 島嶼限制 -admin: - limits: - main: - parameters: "<玩家>" - description: 顯示玩家的島嶼限制 - calc: - parameters: "<玩家>" - description: 重新計算玩家的島嶼限制 - finished: "&a 島嶼重新計算成功完成!" - offset: - unknown: "&c 未知材料或實體[name]。" - main: - description: 允許管理材料和實體的限制偏移 - set: - parameters: "<玩家> <材質|實體> <數字>" - description: 設定材料或實體限制的新偏移 - success: "&a [name] 的限制偏移量設定為 [number]。" - same: "&c [name] 的限制偏移量已經是 [number]。" - add: - parameters: "<玩家> <材質|實體> <數字>" - description: 添加材料或實體限制的偏移量 - success: "&a [name] 的限制偏移量增加到 [number]。" - remove: - parameters: "<玩家> <材質|實體> <數字>" - description: 減少材料或實體限制的偏移 - success: "&a [name] 的限制偏移量減少到 [number]。" - reset: - parameters: "<玩家> <材質|實體>" - description: 刪除材質或實體的偏移 - success: "&a [名稱] 的限制偏移量設定為 0。" - view: - parameters: "<玩家> <材質|實體>" - description: 顯示材料或實體的偏移 - message: "&a [name] 偏移量設定為 [number]。" -island: - limits: - description: 顯示您的島嶼限制 - max-color: "&c" - regular-color: "&a" - block-limit-syntax: "[number]/[limit]" - no-limits: "&c 這個世界沒有限制" - panel: - title-syntax: "[title] [sort]" - entity-group-name-syntax: "[name]" - entity-name-syntax: "[name]" - block-name-syntax: "[name]" - A2Z: a > z - Z2A: z > a - errors: - no-owner: "&c 那島沒有主人" - not-on-island: "&c 該位置沒有設定限制。" - recount: - description: 詳細說明您的島嶼的限制 - now-recounting: "&b 現在重述。這可能需要一段時間,請稍候..." - in-progress: "&c 島回收正在進行中。請稍等..." - time-out: "&c 重新計數時逾時。島真的很大嗎?" +--- +block-limits: + hit-limit: "&c[material]僅限[number]!" +entity-limits: + hit-limit: "&c[entity] 產生僅限於 [number]!" +limits: + panel-title: 島嶼限制 +admin: + limits: + main: + parameters: "<玩家>" + description: 顯示玩家的島嶼限制 + calc: + parameters: "<玩家>" + description: 重新計算玩家的島嶼限制 + finished: "&a 島嶼重新計算成功完成!" + offset: + unknown: "&c 未知材料或實體[name]。" + main: + description: 允許管理材料和實體的限制偏移 + set: + parameters: "<玩家> <材質|實體> <數字>" + description: 設定材料或實體限制的新偏移 + success: "&a [name] 的限制偏移量設定為 [number]。" + same: "&c [name] 的限制偏移量已經是 [number]。" + add: + parameters: "<玩家> <材質|實體> <數字>" + description: 添加材料或實體限制的偏移量 + success: "&a [name] 的限制偏移量增加到 [number]。" + remove: + parameters: "<玩家> <材質|實體> <數字>" + description: 減少材料或實體限制的偏移 + success: "&a [name] 的限制偏移量減少到 [number]。" + reset: + parameters: "<玩家> <材質|實體>" + description: 刪除材質或實體的偏移 + success: "&a [名稱] 的限制偏移量設定為 0。" + view: + parameters: "<玩家> <材質|實體>" + description: 顯示材料或實體的偏移 + message: "&a [name] 偏移量設定為 [number]。" +island: + limits: + description: 顯示您的島嶼限制 + max-color: "&c" + regular-color: "&a" + block-limit-syntax: "[number]/[limit]" + no-limits: "&c 這個世界沒有限制" + panel: + title-syntax: "[title] [sort]" + entity-group-name-syntax: "[name]" + entity-name-syntax: "[name]" + block-name-syntax: "[name]" + A2Z: a > z + Z2A: z > a + errors: + no-owner: "&c 那島沒有主人" + not-on-island: "&c 該位置沒有設定限制。" + recount: + description: 詳細說明您的島嶼的限制 + now-recounting: "&b 現在重述。這可能需要一段時間,請稍候..." + in-progress: "&c 島回收正在進行中。請稍等..." + time-out: "&c 重新計數時逾時。島真的很大嗎?" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 188ba57..d1a2da1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ -name: BentoBox-Limits -main: world.bentobox.limits.LimitsPladdon -version: ${project.version}${build.number} -api-version: "1.21" - -authors: [tastybento] -contributors: ["The BentoBoxWorld Community"] -website: https://bentobox.world -description: ${project.description} +name: BentoBox-Limits +main: world.bentobox.limits.LimitsPladdon +version: ${project.version}${build.number} +api-version: "1.21" + +authors: [tastybento] +contributors: ["The BentoBoxWorld Community"] +website: https://bentobox.world +description: ${project.description} diff --git a/src/test/java/world/bentobox/limits/JoinListenerTest.java b/src/test/java/world/bentobox/limits/JoinListenerTest.java index d78fb6f..f0a7b88 100644 --- a/src/test/java/world/bentobox/limits/JoinListenerTest.java +++ b/src/test/java/world/bentobox/limits/JoinListenerTest.java @@ -1,548 +1,548 @@ -package world.bentobox.limits; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.PluginManager; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import world.bentobox.bentobox.api.addons.AddonDescription; -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.events.island.IslandEvent; -import world.bentobox.bentobox.api.events.team.TeamSetownerEvent; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.IslandsManager; -import world.bentobox.limits.listeners.BlockLimitsListener; -import world.bentobox.limits.listeners.JoinListener; -import world.bentobox.limits.mocks.ServerMocks; -import world.bentobox.limits.objects.IslandBlockCount; - -/** - * @author tastybento - * - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class }) -public class JoinListenerTest { - - @Mock - private Limits addon; - @Mock - private Settings settings; - @Mock - private GameModeAddon bskyblock; - @Mock - private Player player; - - private JoinListener jl; - @Mock - private IslandsManager im; - @Mock - private BlockLimitsListener bll; - @Mock - private IslandBlockCount ibc; - @Mock - private OfflinePlayer owner; - @Mock - private Island island; - @Mock - private PluginManager pim; - private @Nullable UUID uuid = UUID.randomUUID(); - - @Before - public void setUp() { - ServerMocks.newServer(); - jl = new JoinListener(addon); - // Setup addon - when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock)); - when(addon.getGameModeName(any())).thenReturn("bskyblock"); - when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock."); - when(addon.getSettings()).thenReturn(settings); - // Settings - when(settings.getGroupLimitDefinitions()) - .thenReturn(new ArrayList<>(List.of(new EntityGroup("friendly", new HashSet<>(), -1, null)))); - // Island Manager - when(island.getUniqueId()).thenReturn("unique_id"); - when(island.getOwner()).thenReturn(uuid); - when(im.getIsland(any(), any(UUID.class))).thenReturn(island); - when(im.getIslands(any(), any(UUID.class))).thenReturn(List.of(island)); - // Default is that player has island - when(addon.getIslands()).thenReturn(im); - // Player - when(player.getUniqueId()).thenReturn(uuid); - when(player.getName()).thenReturn("tastybento"); - // No permissions by default - when(player.getEffectivePermissions()).thenReturn(Collections.emptySet()); - // bsKyBlock - when(bskyblock.getPermissionPrefix()).thenReturn("bskyblock."); - AddonDescription desc = new AddonDescription.Builder("main", "BSkyBlock", "1.0").build(); - when(bskyblock.getDescription()).thenReturn(desc); - - // Block limit listener - when(addon.getBlockLimitListener()).thenReturn(bll); - when(bll.getIsland(anyString())).thenReturn(ibc); - - // bukkit - PowerMockito.mockStatic(Bukkit.class); - // default is that owner is online - when(owner.isOnline()).thenReturn(true); - when(owner.getPlayer()).thenReturn(player); - when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(owner); - when(Bukkit.getPluginManager()).thenReturn(pim); - - } - - @After - public void tearDown() { - ServerMocks.unsetBukkitServer(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandWrongReason() { - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.BAN); - jl.onNewIsland(e); - verify(island, never()).getWorld(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandRegistered() { - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.REGISTERED); - jl.onNewIsland(e); - verify(island).getWorld(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandResetted() { - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.RESETTED); - jl.onNewIsland(e); - verify(island).getWorld(); - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandCreated() { - when(addon.inGameModeWorld(any())).thenReturn(true); - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); - jl.onNewIsland(e); - verify(island).getWorld(); - verify(owner, times(2)).getPlayer(); - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandCreatedOffline() { - when(owner.isOnline()).thenReturn(false); - when(addon.inGameModeWorld(any())).thenReturn(true); - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); - jl.onNewIsland(e); - verify(island).getWorld(); - verify(owner, never()).getPlayer(); - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnNewIslandCreatedNoNameOrPermPrefix() { - when(addon.getGameModeName(any())).thenReturn(""); - when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock."); - when(addon.inGameModeWorld(any())).thenReturn(true); - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); - jl.onNewIsland(e); - when(addon.getGameModeName(any())).thenReturn("bskyblock"); - when(addon.getGameModePermPrefix(any())).thenReturn(""); - jl.onNewIsland(e); - verify(owner, never()).getPlayer(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onOwnerChange(world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent)}. - */ - @Test - public void testOnOwnerChange() { - TeamSetownerEvent e = mock(TeamSetownerEvent.class); - when(e.getIsland()).thenReturn(island); - when(e.getNewOwner()).thenReturn(UUID.randomUUID()); - jl.onOwnerChange(e); - verify(e, Mockito.times(2)).getIsland(); - verify(e).getNewOwner(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoin() { - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).getGameModes(); - verify(bll).setIsland("unique_id", ibc); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinIBCNull() { - ibc = null; - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).getGameModes(); - verify(bll, never()).setIsland("unique_id", ibc); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermNotLimits() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.my.perm.for.game"); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).getGameModes(); - verify(bll).setIsland("unique_id", ibc); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsWrongSize() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.my.perm.for.game"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).logError( - "Player tastybento has permission: 'bskyblock.island.limit.my.perm.for.game' but format must be 'bskyblock.island.limit.MATERIAL.NUMBER', 'bskyblock.island.limit.ENTITY-TYPE.NUMBER', or 'bskyblock.island.limit.ENTITY-GROUP.NUMBER' Ignoring..."); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsInvalidMaterial() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.mumbo.34"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).logError( - "Player tastybento has permission: 'bskyblock.island.limit.mumbo.34' but MUMBO is not a valid material or entity type/group. Ignoring..."); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsWildcard() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.*"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).logError( - "Player tastybento has permission: 'bskyblock.island.limit.*' but wildcards are not allowed. Ignoring..."); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsNotNumber() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.abc"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon).logError( - "Player tastybento has permission: 'bskyblock.island.limit.STONE.abc' but the last part MUST be an integer! Ignoring..."); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsSuccess() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - verify(ibc).setBlockLimit(eq(Material.STONE), eq(24)); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsSuccessEntity() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.BAT.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - verify(ibc).setEntityLimit(eq(EntityType.BAT), eq(24)); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsSuccessEntityGroup() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.friendly.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - verify(ibc).setEntityGroupLimit(eq("friendly"), eq(24)); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsMultiPerms() { - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); - when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.short_grass.14"); - when(permAtt2.getValue()).thenReturn(true); - perms.add(permAtt2); - PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); - when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.dirt.34"); - when(permAtt3.getValue()).thenReturn(true); - perms.add(permAtt3); - PermissionAttachmentInfo permAtt4 = mock(PermissionAttachmentInfo.class); - when(permAtt4.getPermission()).thenReturn("bskyblock.island.limit.chicken.34"); - when(permAtt4.getValue()).thenReturn(true); - perms.add(permAtt4); - PermissionAttachmentInfo permAtt5 = mock(PermissionAttachmentInfo.class); - when(permAtt5.getPermission()).thenReturn("bskyblock.island.limit.cave_spider.4"); - when(permAtt5.getValue()).thenReturn(true); - perms.add(permAtt5); - PermissionAttachmentInfo permAtt6 = mock(PermissionAttachmentInfo.class); - when(permAtt6.getPermission()).thenReturn("bskyblock.island.limit.cave_spider.4"); - when(permAtt6.getValue()).thenReturn(false); // negative perm - perms.add(permAtt6); - - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - verify(ibc).setBlockLimit(eq(Material.STONE), eq(24)); - verify(ibc).setBlockLimit(eq(Material.SHORT_GRASS), eq(14)); - verify(ibc).setBlockLimit(eq(Material.DIRT), eq(34)); - verify(ibc).setEntityLimit(eq(EntityType.CHICKEN), eq(34)); - verify(ibc).setEntityLimit(eq(EntityType.CAVE_SPIDER), eq(4)); - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsMultiPermsSameMaterial() { - // IBC - set the block limit for STONE to be 25 already - when(ibc.getBlockLimit(any())).thenReturn(25); - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); - when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.STONE.14"); - when(permAtt2.getValue()).thenReturn(true); - perms.add(permAtt2); - PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); - when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.STONE.34"); - when(permAtt3.getValue()).thenReturn(true); - perms.add(permAtt3); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - // Only the limit over 25 should be set - verify(ibc, never()).setBlockLimit(eq(Material.STONE), eq(24)); - verify(ibc, never()).setBlockLimit(eq(Material.STONE), eq(14)); - verify(ibc).setBlockLimit(eq(Material.STONE), eq(34)); - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. - */ - @Test - public void testOnPlayerJoinWithPermLimitsMultiPermsSameEntity() { - // IBC - set the entity limit for BAT to be 25 already - when(ibc.getEntityLimit(any())).thenReturn(25); - Set perms = new HashSet<>(); - PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); - when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.BAT.24"); - when(permAtt.getValue()).thenReturn(true); - perms.add(permAtt); - PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); - when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.BAT.14"); - when(permAtt2.getValue()).thenReturn(true); - perms.add(permAtt2); - PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); - when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.BAT.34"); - when(permAtt3.getValue()).thenReturn(true); - perms.add(permAtt3); - when(player.getEffectivePermissions()).thenReturn(perms); - PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); - jl.onPlayerJoin(e); - verify(addon, never()).logError(anyString()); - // Only the limit over 25 should be set - verify(ibc, never()).setEntityLimit(eq(EntityType.BAT), eq(24)); - verify(ibc, never()).setEntityLimit(eq(EntityType.BAT), eq(14)); - verify(ibc).setEntityLimit(eq(EntityType.BAT), eq(34)); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnUnregisterIslandNotUnregistered() { - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.BAN); - jl.onUnregisterIsland(e); - verify(island, never()).getWorld(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnUnregisterIslandNotInWorld() { - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); - jl.onUnregisterIsland(e); - verify(island).getWorld(); - verify(addon, never()).getBlockLimitListener(); - } - - /** - * Test method for - * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnUnregisterIslandInWorld() { - @SuppressWarnings("unchecked") - Map map = mock(Map.class); - when(ibc.getBlockLimits()).thenReturn(map); - when(addon.inGameModeWorld(any())).thenReturn(true); - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); - jl.onUnregisterIsland(e); - verify(island).getWorld(); - verify(addon).getBlockLimitListener(); - verify(map).clear(); - - } - - /** - * Test method for {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. - */ - @Test - public void testOnUnregisterIslandInWorldNullIBC() { - when(bll.getIsland(anyString())).thenReturn(null); - @SuppressWarnings("unchecked") - Map map = mock(Map.class); - when(ibc.getBlockLimits()).thenReturn(map); - when(addon.inGameModeWorld(any())).thenReturn(true); - IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); - jl.onUnregisterIsland(e); - verify(island).getWorld(); - verify(addon).getBlockLimitListener(); - verify(map, never()).clear(); - - } - -} +package world.bentobox.limits; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.PluginManager; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.events.team.TeamSetownerEvent; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.limits.listeners.BlockLimitsListener; +import world.bentobox.limits.listeners.JoinListener; +import world.bentobox.limits.mocks.ServerMocks; +import world.bentobox.limits.objects.IslandBlockCount; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class }) +public class JoinListenerTest { + + @Mock + private Limits addon; + @Mock + private Settings settings; + @Mock + private GameModeAddon bskyblock; + @Mock + private Player player; + + private JoinListener jl; + @Mock + private IslandsManager im; + @Mock + private BlockLimitsListener bll; + @Mock + private IslandBlockCount ibc; + @Mock + private OfflinePlayer owner; + @Mock + private Island island; + @Mock + private PluginManager pim; + private @Nullable UUID uuid = UUID.randomUUID(); + + @Before + public void setUp() { + ServerMocks.newServer(); + jl = new JoinListener(addon); + // Setup addon + when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock)); + when(addon.getGameModeName(any())).thenReturn("bskyblock"); + when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock."); + when(addon.getSettings()).thenReturn(settings); + // Settings + when(settings.getGroupLimitDefinitions()) + .thenReturn(new ArrayList<>(List.of(new EntityGroup("friendly", new HashSet<>(), -1, null)))); + // Island Manager + when(island.getUniqueId()).thenReturn("unique_id"); + when(island.getOwner()).thenReturn(uuid); + when(im.getIsland(any(), any(UUID.class))).thenReturn(island); + when(im.getIslands(any(), any(UUID.class))).thenReturn(List.of(island)); + // Default is that player has island + when(addon.getIslands()).thenReturn(im); + // Player + when(player.getUniqueId()).thenReturn(uuid); + when(player.getName()).thenReturn("tastybento"); + // No permissions by default + when(player.getEffectivePermissions()).thenReturn(Collections.emptySet()); + // bsKyBlock + when(bskyblock.getPermissionPrefix()).thenReturn("bskyblock."); + AddonDescription desc = new AddonDescription.Builder("main", "BSkyBlock", "1.0").build(); + when(bskyblock.getDescription()).thenReturn(desc); + + // Block limit listener + when(addon.getBlockLimitListener()).thenReturn(bll); + when(bll.getIsland(anyString())).thenReturn(ibc); + + // bukkit + PowerMockito.mockStatic(Bukkit.class); + // default is that owner is online + when(owner.isOnline()).thenReturn(true); + when(owner.getPlayer()).thenReturn(player); + when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(owner); + when(Bukkit.getPluginManager()).thenReturn(pim); + + } + + @After + public void tearDown() { + ServerMocks.unsetBukkitServer(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandWrongReason() { + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.BAN); + jl.onNewIsland(e); + verify(island, never()).getWorld(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandRegistered() { + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.REGISTERED); + jl.onNewIsland(e); + verify(island).getWorld(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandResetted() { + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.RESETTED); + jl.onNewIsland(e); + verify(island).getWorld(); + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandCreated() { + when(addon.inGameModeWorld(any())).thenReturn(true); + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); + jl.onNewIsland(e); + verify(island).getWorld(); + verify(owner, times(2)).getPlayer(); + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandCreatedOffline() { + when(owner.isOnline()).thenReturn(false); + when(addon.inGameModeWorld(any())).thenReturn(true); + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); + jl.onNewIsland(e); + verify(island).getWorld(); + verify(owner, never()).getPlayer(); + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnNewIslandCreatedNoNameOrPermPrefix() { + when(addon.getGameModeName(any())).thenReturn(""); + when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock."); + when(addon.inGameModeWorld(any())).thenReturn(true); + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED); + jl.onNewIsland(e); + when(addon.getGameModeName(any())).thenReturn("bskyblock"); + when(addon.getGameModePermPrefix(any())).thenReturn(""); + jl.onNewIsland(e); + verify(owner, never()).getPlayer(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onOwnerChange(world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent)}. + */ + @Test + public void testOnOwnerChange() { + TeamSetownerEvent e = mock(TeamSetownerEvent.class); + when(e.getIsland()).thenReturn(island); + when(e.getNewOwner()).thenReturn(UUID.randomUUID()); + jl.onOwnerChange(e); + verify(e, Mockito.times(2)).getIsland(); + verify(e).getNewOwner(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoin() { + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).getGameModes(); + verify(bll).setIsland("unique_id", ibc); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinIBCNull() { + ibc = null; + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).getGameModes(); + verify(bll, never()).setIsland("unique_id", ibc); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermNotLimits() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.my.perm.for.game"); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).getGameModes(); + verify(bll).setIsland("unique_id", ibc); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsWrongSize() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.my.perm.for.game"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).logError( + "Player tastybento has permission: 'bskyblock.island.limit.my.perm.for.game' but format must be 'bskyblock.island.limit.MATERIAL.NUMBER', 'bskyblock.island.limit.ENTITY-TYPE.NUMBER', or 'bskyblock.island.limit.ENTITY-GROUP.NUMBER' Ignoring..."); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsInvalidMaterial() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.mumbo.34"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).logError( + "Player tastybento has permission: 'bskyblock.island.limit.mumbo.34' but MUMBO is not a valid material or entity type/group. Ignoring..."); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsWildcard() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.*"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).logError( + "Player tastybento has permission: 'bskyblock.island.limit.*' but wildcards are not allowed. Ignoring..."); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsNotNumber() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.abc"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon).logError( + "Player tastybento has permission: 'bskyblock.island.limit.STONE.abc' but the last part MUST be an integer! Ignoring..."); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsSuccess() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + verify(ibc).setBlockLimit(eq(Material.STONE), eq(24)); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsSuccessEntity() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.BAT.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + verify(ibc).setEntityLimit(eq(EntityType.BAT), eq(24)); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsSuccessEntityGroup() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.friendly.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + verify(ibc).setEntityGroupLimit(eq("friendly"), eq(24)); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsMultiPerms() { + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); + when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.short_grass.14"); + when(permAtt2.getValue()).thenReturn(true); + perms.add(permAtt2); + PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); + when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.dirt.34"); + when(permAtt3.getValue()).thenReturn(true); + perms.add(permAtt3); + PermissionAttachmentInfo permAtt4 = mock(PermissionAttachmentInfo.class); + when(permAtt4.getPermission()).thenReturn("bskyblock.island.limit.chicken.34"); + when(permAtt4.getValue()).thenReturn(true); + perms.add(permAtt4); + PermissionAttachmentInfo permAtt5 = mock(PermissionAttachmentInfo.class); + when(permAtt5.getPermission()).thenReturn("bskyblock.island.limit.cave_spider.4"); + when(permAtt5.getValue()).thenReturn(true); + perms.add(permAtt5); + PermissionAttachmentInfo permAtt6 = mock(PermissionAttachmentInfo.class); + when(permAtt6.getPermission()).thenReturn("bskyblock.island.limit.cave_spider.4"); + when(permAtt6.getValue()).thenReturn(false); // negative perm + perms.add(permAtt6); + + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + verify(ibc).setBlockLimit(eq(Material.STONE), eq(24)); + verify(ibc).setBlockLimit(eq(Material.SHORT_GRASS), eq(14)); + verify(ibc).setBlockLimit(eq(Material.DIRT), eq(34)); + verify(ibc).setEntityLimit(eq(EntityType.CHICKEN), eq(34)); + verify(ibc).setEntityLimit(eq(EntityType.CAVE_SPIDER), eq(4)); + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsMultiPermsSameMaterial() { + // IBC - set the block limit for STONE to be 25 already + when(ibc.getBlockLimit(any())).thenReturn(25); + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); + when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.STONE.14"); + when(permAtt2.getValue()).thenReturn(true); + perms.add(permAtt2); + PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); + when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.STONE.34"); + when(permAtt3.getValue()).thenReturn(true); + perms.add(permAtt3); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + // Only the limit over 25 should be set + verify(ibc, never()).setBlockLimit(eq(Material.STONE), eq(24)); + verify(ibc, never()).setBlockLimit(eq(Material.STONE), eq(14)); + verify(ibc).setBlockLimit(eq(Material.STONE), eq(34)); + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. + */ + @Test + public void testOnPlayerJoinWithPermLimitsMultiPermsSameEntity() { + // IBC - set the entity limit for BAT to be 25 already + when(ibc.getEntityLimit(any())).thenReturn(25); + Set perms = new HashSet<>(); + PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class); + when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.BAT.24"); + when(permAtt.getValue()).thenReturn(true); + perms.add(permAtt); + PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); + when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.BAT.14"); + when(permAtt2.getValue()).thenReturn(true); + perms.add(permAtt2); + PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); + when(permAtt3.getPermission()).thenReturn("bskyblock.island.limit.BAT.34"); + when(permAtt3.getValue()).thenReturn(true); + perms.add(permAtt3); + when(player.getEffectivePermissions()).thenReturn(perms); + PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); + jl.onPlayerJoin(e); + verify(addon, never()).logError(anyString()); + // Only the limit over 25 should be set + verify(ibc, never()).setEntityLimit(eq(EntityType.BAT), eq(24)); + verify(ibc, never()).setEntityLimit(eq(EntityType.BAT), eq(14)); + verify(ibc).setEntityLimit(eq(EntityType.BAT), eq(34)); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnUnregisterIslandNotUnregistered() { + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.BAN); + jl.onUnregisterIsland(e); + verify(island, never()).getWorld(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnUnregisterIslandNotInWorld() { + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); + jl.onUnregisterIsland(e); + verify(island).getWorld(); + verify(addon, never()).getBlockLimitListener(); + } + + /** + * Test method for + * {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnUnregisterIslandInWorld() { + @SuppressWarnings("unchecked") + Map map = mock(Map.class); + when(ibc.getBlockLimits()).thenReturn(map); + when(addon.inGameModeWorld(any())).thenReturn(true); + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); + jl.onUnregisterIsland(e); + verify(island).getWorld(); + verify(addon).getBlockLimitListener(); + verify(map).clear(); + + } + + /** + * Test method for {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. + */ + @Test + public void testOnUnregisterIslandInWorldNullIBC() { + when(bll.getIsland(anyString())).thenReturn(null); + @SuppressWarnings("unchecked") + Map map = mock(Map.class); + when(ibc.getBlockLimits()).thenReturn(map); + when(addon.inGameModeWorld(any())).thenReturn(true); + IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.UNREGISTERED); + jl.onUnregisterIsland(e); + verify(island).getWorld(); + verify(addon).getBlockLimitListener(); + verify(map, never()).clear(); + + } + +} diff --git a/src/test/java/world/bentobox/limits/LimitsTest.java b/src/test/java/world/bentobox/limits/LimitsTest.java index 30837b7..e0a9c18 100644 --- a/src/test/java/world/bentobox/limits/LimitsTest.java +++ b/src/test/java/world/bentobox/limits/LimitsTest.java @@ -1,366 +1,366 @@ -package world.bentobox.limits; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Comparator; -import java.util.Optional; -import java.util.UUID; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.UnsafeValues; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.PluginManager; -import org.bukkit.scheduler.BukkitScheduler; -import org.eclipse.jdt.annotation.NonNull; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.Settings; -import world.bentobox.bentobox.api.addons.AddonDescription; -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.AddonsManager; -import world.bentobox.bentobox.managers.CommandsManager; -import world.bentobox.bentobox.managers.FlagsManager; -import world.bentobox.bentobox.managers.IslandWorldManager; -import world.bentobox.bentobox.managers.IslandsManager; -import world.bentobox.bentobox.managers.PlaceholdersManager; -import world.bentobox.limits.mocks.ServerMocks; - -/** - * @author tastybento - * - */ -@SuppressWarnings("deprecation") -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) -public class LimitsTest { - private static File jFile; - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - @Mock - private BentoBox plugin; - @Mock - private FlagsManager fm; - @Mock - private GameModeAddon gameMode; - @Mock - private AddonsManager am; - @Mock - private BukkitScheduler scheduler; - - @Mock - private Settings pluginSettings; - @Mock - private PlaceholdersManager phm; - @Mock - private CompositeCommand cmd; - @Mock - private CompositeCommand adminCmd; - @Mock - private World world; - private UUID uuid; - - @Mock - private PluginManager pim; - - - private Limits addon; - - @BeforeClass - public static void beforeClass() throws Exception { - cleanUp(); - // Make the addon jar - jFile = new File("addon.jar"); - // Copy over config file from src folder - Path fromPath = Paths.get("src/main/resources/config.yml"); - Path path = Paths.get("config.yml"); - Files.copy(fromPath, path); - try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { - //Added the new files to the jar. - try (FileInputStream fis = new FileInputStream(path.toFile())) { - byte[] buffer = new byte[1024]; - int bytesRead = 0; - JarEntry entry = new JarEntry(path.toString()); - tempJarOutputStream.putNextEntry(entry); - while((bytesRead = fis.read(buffer)) != -1) { - tempJarOutputStream.write(buffer, 0, bytesRead); - } - } - } - } - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - Server server = ServerMocks.newServer(); - // Set up plugin - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); - - // The database type has to be created one line before the thenReturn() to work! - DatabaseType value = DatabaseType.JSON; - when(plugin.getSettings()).thenReturn(pluginSettings); - when(pluginSettings.getDatabaseType()).thenReturn(value); - - //when(plugin.isEnabled()).thenReturn(true); - // Command manager - CommandsManager cm = mock(CommandsManager.class); - when(plugin.getCommandsManager()).thenReturn(cm); - - // Player - Player p = mock(Player.class); - // Sometimes use Mockito.withSettings().verboseLogging() - when(user.isOp()).thenReturn(false); - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getPlayer()).thenReturn(p); - when(user.getName()).thenReturn("tastybento"); - User.setPlugin(plugin); - - // Island World Manager - IslandWorldManager iwm = mock(IslandWorldManager.class); - when(plugin.getIWM()).thenReturn(iwm); - - - - // Player has island to begin with - when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); - when(plugin.getIslands()).thenReturn(im); - - // Locales - // Return the reference (USE THIS IN THE FUTURE) - when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); - - // Server - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getServer()).thenReturn(server); - when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); - when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); - - // Addon - addon = new Limits(); - File dataFolder = new File("addons/Level"); - addon.setDataFolder(dataFolder); - addon.setFile(jFile); - AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build(); - addon.setDescription(desc); - - // Addons manager - when(plugin.getAddonsManager()).thenReturn(am); - // One game mode - when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); - AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build(); - when(gameMode.getDescription()).thenReturn(desc2); - when(gameMode.getOverWorld()).thenReturn(world); - - // Player command - @NonNull - Optional opCmd = Optional.of(cmd); - when(gameMode.getPlayerCommand()).thenReturn(opCmd); - // Admin command - Optional opAdminCmd = Optional.of(adminCmd); - when(gameMode.getAdminCommand()).thenReturn(opAdminCmd); - - // Perm prefix - when(gameMode.getPermissionPrefix()).thenReturn("bskyblock."); - - // Flags manager - when(plugin.getFlagsManager()).thenReturn(fm); - when(fm.getFlags()).thenReturn(Collections.emptyList()); - - - // Bukkit - when(Bukkit.getScheduler()).thenReturn(scheduler); - ItemMeta meta = mock(ItemMeta.class); - ItemFactory itemFactory = mock(ItemFactory.class); - when(itemFactory.getItemMeta(any())).thenReturn(meta); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - UnsafeValues unsafe = mock(UnsafeValues.class); - when(unsafe.getDataVersion()).thenReturn(777); - when(Bukkit.getUnsafe()).thenReturn(unsafe); - when(Bukkit.getPluginManager()).thenReturn(pim); - - // placeholders - when(plugin.getPlaceholdersManager()).thenReturn(phm); - - // World - when(world.getName()).thenReturn("bskyblock-world"); - // Island - when(island.getWorld()).thenReturn(world); - when(island.getOwner()).thenReturn(uuid); - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - ServerMocks.unsetBukkitServer(); - User.clearUsers(); - Mockito.framework().clearInlineMocks(); - deleteAll(new File("database")); - } - - @AfterClass - public static void cleanUp() throws Exception { - new File("addon.jar").delete(); - new File("config.yml").delete(); - deleteAll(new File("addons")); - } - - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - - /** - * Test method for {@link world.bentobox.limits.Limits#onEnable()}. - */ - @Test - public void testOnEnable() { - addon.onEnable(); - File f = new File("config.yml"); - assertTrue(f.exists()); - - } - - /** - * Test method for {@link world.bentobox.limits.Limits#onDisable()}. - */ - @Test - public void testOnDisable() { - addon.onDisable(); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getSettings()}. - */ - @Test - public void testGetSettings() { - assertNull(addon.getSettings()); - addon.onEnable(); - world.bentobox.limits.Settings set = addon.getSettings(); - assertFalse(set.getLimits().isEmpty()); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getGameModes()}. - */ - @Test - public void testGetGameModes() { - assertTrue(addon.getGameModes().isEmpty()); - addon.onEnable(); - assertFalse(addon.getGameModes().isEmpty()); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getBlockLimitListener()}. - */ - @Test - public void testGetBlockLimitListener() { - assertNull(addon.getBlockLimitListener()); - addon.onEnable(); - assertNotNull(addon.getBlockLimitListener()); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#inGameModeWorld(org.bukkit.World)}. - */ - @Test - public void testInGameModeWorld() { - addon.onEnable(); - assertFalse(addon.inGameModeWorld(world)); - when(gameMode.inWorld(world)).thenReturn(true); - assertTrue(addon.inGameModeWorld(world)); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getGameModeName(org.bukkit.World)}. - */ - @Test - public void testGetGameModeName() { - when(gameMode.inWorld(world)).thenReturn(true); - assertTrue(addon.getGameModeName(world).isEmpty()); - addon.onEnable(); - assertEquals("BSkyBlock", addon.getGameModeName(world)); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getGameModePermPrefix(org.bukkit.World)}. - */ - @Test - public void testGetGameModePermPrefix() { - when(gameMode.inWorld(world)).thenReturn(true); - addon.onEnable(); - assertEquals("bskyblock.", addon.getGameModePermPrefix(world)); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#isCoveredGameMode(java.lang.String)}. - */ - @Test - public void testIsCoveredGameMode() { - assertFalse(addon.isCoveredGameMode("BSkyBlock")); - addon.onEnable(); - assertTrue(addon.isCoveredGameMode("BSkyBlock")); - } - - /** - * Test method for {@link world.bentobox.limits.Limits#getJoinListener()}. - */ - @Test - public void testGetJoinListener() { - assertNull(addon.getJoinListener()); - addon.onEnable(); - assertNotNull(addon.getJoinListener()); - } - -} +package world.bentobox.limits; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.UnsafeValues; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.AddonsManager; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.FlagsManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.limits.mocks.ServerMocks; + +/** + * @author tastybento + * + */ +@SuppressWarnings("deprecation") +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) +public class LimitsTest { + private static File jFile; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private BentoBox plugin; + @Mock + private FlagsManager fm; + @Mock + private GameModeAddon gameMode; + @Mock + private AddonsManager am; + @Mock + private BukkitScheduler scheduler; + + @Mock + private Settings pluginSettings; + @Mock + private PlaceholdersManager phm; + @Mock + private CompositeCommand cmd; + @Mock + private CompositeCommand adminCmd; + @Mock + private World world; + private UUID uuid; + + @Mock + private PluginManager pim; + + + private Limits addon; + + @BeforeClass + public static void beforeClass() throws Exception { + cleanUp(); + // Make the addon jar + jFile = new File("addon.jar"); + // Copy over config file from src folder + Path fromPath = Paths.get("src/main/resources/config.yml"); + Path path = Paths.get("config.yml"); + Files.copy(fromPath, path); + try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { + //Added the new files to the jar. + try (FileInputStream fis = new FileInputStream(path.toFile())) { + byte[] buffer = new byte[1024]; + int bytesRead = 0; + JarEntry entry = new JarEntry(path.toString()); + tempJarOutputStream.putNextEntry(entry); + while((bytesRead = fis.read(buffer)) != -1) { + tempJarOutputStream.write(buffer, 0, bytesRead); + } + } + } + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + Server server = ServerMocks.newServer(); + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(pluginSettings); + when(pluginSettings.getDatabaseType()).thenReturn(value); + + //when(plugin.isEnabled()).thenReturn(true); + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Player + Player p = mock(Player.class); + // Sometimes use Mockito.withSettings().verboseLogging() + when(user.isOp()).thenReturn(false); + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + User.setPlugin(plugin); + + // Island World Manager + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + + + + // Player has island to begin with + when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); + when(plugin.getIslands()).thenReturn(im); + + // Locales + // Return the reference (USE THIS IN THE FUTURE) + when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + // Server + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); + when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); + + // Addon + addon = new Limits(); + File dataFolder = new File("addons/Level"); + addon.setDataFolder(dataFolder); + addon.setFile(jFile); + AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build(); + addon.setDescription(desc); + + // Addons manager + when(plugin.getAddonsManager()).thenReturn(am); + // One game mode + when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); + AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build(); + when(gameMode.getDescription()).thenReturn(desc2); + when(gameMode.getOverWorld()).thenReturn(world); + + // Player command + @NonNull + Optional opCmd = Optional.of(cmd); + when(gameMode.getPlayerCommand()).thenReturn(opCmd); + // Admin command + Optional opAdminCmd = Optional.of(adminCmd); + when(gameMode.getAdminCommand()).thenReturn(opAdminCmd); + + // Perm prefix + when(gameMode.getPermissionPrefix()).thenReturn("bskyblock."); + + // Flags manager + when(plugin.getFlagsManager()).thenReturn(fm); + when(fm.getFlags()).thenReturn(Collections.emptyList()); + + + // Bukkit + when(Bukkit.getScheduler()).thenReturn(scheduler); + ItemMeta meta = mock(ItemMeta.class); + ItemFactory itemFactory = mock(ItemFactory.class); + when(itemFactory.getItemMeta(any())).thenReturn(meta); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + UnsafeValues unsafe = mock(UnsafeValues.class); + when(unsafe.getDataVersion()).thenReturn(777); + when(Bukkit.getUnsafe()).thenReturn(unsafe); + when(Bukkit.getPluginManager()).thenReturn(pim); + + // placeholders + when(plugin.getPlaceholdersManager()).thenReturn(phm); + + // World + when(world.getName()).thenReturn("bskyblock-world"); + // Island + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(uuid); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + ServerMocks.unsetBukkitServer(); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + deleteAll(new File("database")); + } + + @AfterClass + public static void cleanUp() throws Exception { + new File("addon.jar").delete(); + new File("config.yml").delete(); + deleteAll(new File("addons")); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.limits.Limits#onEnable()}. + */ + @Test + public void testOnEnable() { + addon.onEnable(); + File f = new File("config.yml"); + assertTrue(f.exists()); + + } + + /** + * Test method for {@link world.bentobox.limits.Limits#onDisable()}. + */ + @Test + public void testOnDisable() { + addon.onDisable(); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getSettings()}. + */ + @Test + public void testGetSettings() { + assertNull(addon.getSettings()); + addon.onEnable(); + world.bentobox.limits.Settings set = addon.getSettings(); + assertFalse(set.getLimits().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getGameModes()}. + */ + @Test + public void testGetGameModes() { + assertTrue(addon.getGameModes().isEmpty()); + addon.onEnable(); + assertFalse(addon.getGameModes().isEmpty()); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getBlockLimitListener()}. + */ + @Test + public void testGetBlockLimitListener() { + assertNull(addon.getBlockLimitListener()); + addon.onEnable(); + assertNotNull(addon.getBlockLimitListener()); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#inGameModeWorld(org.bukkit.World)}. + */ + @Test + public void testInGameModeWorld() { + addon.onEnable(); + assertFalse(addon.inGameModeWorld(world)); + when(gameMode.inWorld(world)).thenReturn(true); + assertTrue(addon.inGameModeWorld(world)); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getGameModeName(org.bukkit.World)}. + */ + @Test + public void testGetGameModeName() { + when(gameMode.inWorld(world)).thenReturn(true); + assertTrue(addon.getGameModeName(world).isEmpty()); + addon.onEnable(); + assertEquals("BSkyBlock", addon.getGameModeName(world)); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getGameModePermPrefix(org.bukkit.World)}. + */ + @Test + public void testGetGameModePermPrefix() { + when(gameMode.inWorld(world)).thenReturn(true); + addon.onEnable(); + assertEquals("bskyblock.", addon.getGameModePermPrefix(world)); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#isCoveredGameMode(java.lang.String)}. + */ + @Test + public void testIsCoveredGameMode() { + assertFalse(addon.isCoveredGameMode("BSkyBlock")); + addon.onEnable(); + assertTrue(addon.isCoveredGameMode("BSkyBlock")); + } + + /** + * Test method for {@link world.bentobox.limits.Limits#getJoinListener()}. + */ + @Test + public void testGetJoinListener() { + assertNull(addon.getJoinListener()); + addon.onEnable(); + assertNotNull(addon.getJoinListener()); + } + +} diff --git a/src/test/java/world/bentobox/limits/commands/player/LimitTabTest.java b/src/test/java/world/bentobox/limits/commands/player/LimitTabTest.java index ab92735..65b09ac 100644 --- a/src/test/java/world/bentobox/limits/commands/player/LimitTabTest.java +++ b/src/test/java/world/bentobox/limits/commands/player/LimitTabTest.java @@ -1,111 +1,111 @@ -package world.bentobox.limits.commands.player; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Collections; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.IslandWorldManager; -import world.bentobox.limits.Limits; -import world.bentobox.limits.Settings; -import world.bentobox.limits.objects.IslandBlockCount; - -@RunWith(PowerMockRunner.class) -@PrepareForTest( Bukkit.class ) -public class LimitTabTest { - - @Mock - private Limits addon; - - private LimitTab lp; - - @Mock - private Island island; - @Mock - private World world; - @Mock - private World nether; - @Mock - private World end; - @Mock - private BentoBox plugin; - @Mock - private IslandWorldManager iwm; - @Mock - private Settings settings; - - @Before - public void setUp() { - // Island - when(island.getWorld()).thenReturn(world); - // Addon - when(addon.getPlugin()).thenReturn(plugin); - when(addon.getSettings()).thenReturn(settings); - when(settings.getLimits()).thenReturn(Collections.emptyMap()); - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.isNetherIslands(any())).thenReturn(true); - when(iwm.isEndIslands(any())).thenReturn(true); - when(iwm.getNetherWorld(eq(world))).thenReturn(nether); - when(iwm.getEndWorld(eq(world))).thenReturn(end); - // Worlds - Entity entity = mock(Entity.class); - when(entity.getType()).thenReturn(EntityType.BAT); - when(entity.getLocation()).thenReturn(mock(Location.class)); - when(world.getEntities()).thenReturn(Collections.singletonList(entity)); - when(nether.getEntities()).thenReturn(Collections.singletonList(entity)); - when(end.getEntities()).thenReturn(Collections.singletonList(entity)); - lp = new LimitTab(addon, new IslandBlockCount("", ""), Collections.emptyMap(), island, world, null, LimitTab.SORT_BY.A2Z); - } - - @After - public void tearDown() { - } - - @Test - @Ignore - public void testShowLimits() { - fail("Not yet implemented"); - } - - @Test - public void testGetCountInIslandSpace() { - when(island.inIslandSpace(any(Location.class))).thenReturn(true); - EntityType ent = EntityType.BAT; - assertEquals(3L, lp.getCount(island, ent)); - ent = EntityType.GHAST; - assertEquals(0L, lp.getCount(island, ent)); - when(iwm.isEndIslands(any())).thenReturn(false); - ent = EntityType.BAT; - assertEquals(2L, lp.getCount(island, ent)); - when(iwm.isNetherIslands(any())).thenReturn(false); - ent = EntityType.BAT; - assertEquals(1L, lp.getCount(island, ent)); - } - - @Test - public void testGetCountNotInIslandSpace() { - EntityType ent = EntityType.BAT; - assertEquals(0L, lp.getCount(island, ent)); - } - -} +package world.bentobox.limits.commands.player; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.limits.Limits; +import world.bentobox.limits.Settings; +import world.bentobox.limits.objects.IslandBlockCount; + +@RunWith(PowerMockRunner.class) +@PrepareForTest( Bukkit.class ) +public class LimitTabTest { + + @Mock + private Limits addon; + + private LimitTab lp; + + @Mock + private Island island; + @Mock + private World world; + @Mock + private World nether; + @Mock + private World end; + @Mock + private BentoBox plugin; + @Mock + private IslandWorldManager iwm; + @Mock + private Settings settings; + + @Before + public void setUp() { + // Island + when(island.getWorld()).thenReturn(world); + // Addon + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getSettings()).thenReturn(settings); + when(settings.getLimits()).thenReturn(Collections.emptyMap()); + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.isNetherIslands(any())).thenReturn(true); + when(iwm.isEndIslands(any())).thenReturn(true); + when(iwm.getNetherWorld(eq(world))).thenReturn(nether); + when(iwm.getEndWorld(eq(world))).thenReturn(end); + // Worlds + Entity entity = mock(Entity.class); + when(entity.getType()).thenReturn(EntityType.BAT); + when(entity.getLocation()).thenReturn(mock(Location.class)); + when(world.getEntities()).thenReturn(Collections.singletonList(entity)); + when(nether.getEntities()).thenReturn(Collections.singletonList(entity)); + when(end.getEntities()).thenReturn(Collections.singletonList(entity)); + lp = new LimitTab(addon, new IslandBlockCount("", ""), Collections.emptyMap(), island, world, null, LimitTab.SORT_BY.A2Z); + } + + @After + public void tearDown() { + } + + @Test + @Ignore + public void testShowLimits() { + fail("Not yet implemented"); + } + + @Test + public void testGetCountInIslandSpace() { + when(island.inIslandSpace(any(Location.class))).thenReturn(true); + EntityType ent = EntityType.BAT; + assertEquals(3L, lp.getCount(island, ent)); + ent = EntityType.GHAST; + assertEquals(0L, lp.getCount(island, ent)); + when(iwm.isEndIslands(any())).thenReturn(false); + ent = EntityType.BAT; + assertEquals(2L, lp.getCount(island, ent)); + when(iwm.isNetherIslands(any())).thenReturn(false); + ent = EntityType.BAT; + assertEquals(1L, lp.getCount(island, ent)); + } + + @Test + public void testGetCountNotInIslandSpace() { + EntityType ent = EntityType.BAT; + assertEquals(0L, lp.getCount(island, ent)); + } + +} diff --git a/src/test/java/world/bentobox/limits/listeners/EntityLimitListenerTest.java b/src/test/java/world/bentobox/limits/listeners/EntityLimitListenerTest.java index f3337af..64dacae 100644 --- a/src/test/java/world/bentobox/limits/listeners/EntityLimitListenerTest.java +++ b/src/test/java/world/bentobox/limits/listeners/EntityLimitListenerTest.java @@ -1,146 +1,146 @@ -package world.bentobox.limits.listeners; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.limits.Limits; -import world.bentobox.limits.Settings; -import world.bentobox.limits.listeners.EntityLimitListener.AtLimitResult; -import world.bentobox.limits.objects.IslandBlockCount; - -@RunWith(PowerMockRunner.class) -@PrepareForTest( Bukkit.class ) -public class EntityLimitListenerTest { - @Mock - private Limits addon; - private EntityLimitListener ell; - @Mock - private Island island; - @Mock - private LivingEntity ent; - @Mock - private BlockLimitsListener bll; - @Mock - private World world; - private List collection; - @Mock - private Location location; - private IslandBlockCount ibc; - - - @Before - public void setUp() throws Exception { - // Entity - when(ent.getType()).thenReturn(EntityType.ENDERMAN); - when(ent.getLocation()).thenReturn(location); - // Island - when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); - when(island.inIslandSpace(any(Location.class))).thenReturn(true); - - ibc = new IslandBlockCount("",""); - when(bll.getIsland(anyString())).thenReturn(ibc); - when(addon.getBlockLimitListener()).thenReturn(bll); - - FileConfiguration config = new YamlConfiguration(); - config.load("src/main/resources/config.yml"); - // Settings - when(addon.getConfig()).thenReturn(config); - Settings settings = new Settings(addon); - when(addon.getSettings()).thenReturn(settings); - - // World - when(ent.getWorld()).thenReturn(world); - collection = new ArrayList<>(); - collection.add(ent); - collection.add(ent); - collection.add(ent); - collection.add(ent); - when(world.getNearbyEntities(any())).thenReturn(collection); - - ell = new EntityLimitListener(addon); - } - - /** - * Test for {@link EntityLimitListener#atLimit(Island, Entity)} - */ - @Test - public void testAtLimitUnderLimit() { - AtLimitResult result = ell.atLimit(island, ent); - assertFalse(result.hit()); - } - - /** - * Test for {@link EntityLimitListener#atLimit(Island, Entity)} - */ - @Test - public void testAtLimitAtLimit() { - collection.add(ent); - AtLimitResult result = ell.atLimit(island, ent); - assertTrue(result.hit()); - assertEquals(EntityType.ENDERMAN, result.getTypelimit().getKey()); - assertEquals(Integer.valueOf(5), result.getTypelimit().getValue()); - - } - - /** - * Test for {@link EntityLimitListener#atLimit(Island, Entity)} - */ - @Test - public void testAtLimitUnderLimitIslandLimit() { - ibc.setEntityLimit(EntityType.ENDERMAN, 6); - AtLimitResult result = ell.atLimit(island, ent); - assertFalse(result.hit()); - } - - /** - * Test for {@link EntityLimitListener#atLimit(Island, Entity)} - */ - @Test - public void testAtLimitAtLimitIslandLimitNotAtLimit() { - ibc.setEntityLimit(EntityType.ENDERMAN, 6); - collection.add(ent); - AtLimitResult result = ell.atLimit(island, ent); - assertFalse(result.hit()); - } - - /** - * Test for {@link EntityLimitListener#atLimit(Island, Entity)} - */ - @Test - public void testAtLimitAtLimitIslandLimit() { - ibc.setEntityLimit(EntityType.ENDERMAN, 6); - collection.add(ent); - collection.add(ent); - AtLimitResult result = ell.atLimit(island, ent); - assertTrue(result.hit()); - assertEquals(EntityType.ENDERMAN, result.getTypelimit().getKey()); - assertEquals(Integer.valueOf(6), result.getTypelimit().getValue()); - - } - - -} +package world.bentobox.limits.listeners; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.limits.Limits; +import world.bentobox.limits.Settings; +import world.bentobox.limits.listeners.EntityLimitListener.AtLimitResult; +import world.bentobox.limits.objects.IslandBlockCount; + +@RunWith(PowerMockRunner.class) +@PrepareForTest( Bukkit.class ) +public class EntityLimitListenerTest { + @Mock + private Limits addon; + private EntityLimitListener ell; + @Mock + private Island island; + @Mock + private LivingEntity ent; + @Mock + private BlockLimitsListener bll; + @Mock + private World world; + private List collection; + @Mock + private Location location; + private IslandBlockCount ibc; + + + @Before + public void setUp() throws Exception { + // Entity + when(ent.getType()).thenReturn(EntityType.ENDERMAN); + when(ent.getLocation()).thenReturn(location); + // Island + when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); + when(island.inIslandSpace(any(Location.class))).thenReturn(true); + + ibc = new IslandBlockCount("",""); + when(bll.getIsland(anyString())).thenReturn(ibc); + when(addon.getBlockLimitListener()).thenReturn(bll); + + FileConfiguration config = new YamlConfiguration(); + config.load("src/main/resources/config.yml"); + // Settings + when(addon.getConfig()).thenReturn(config); + Settings settings = new Settings(addon); + when(addon.getSettings()).thenReturn(settings); + + // World + when(ent.getWorld()).thenReturn(world); + collection = new ArrayList<>(); + collection.add(ent); + collection.add(ent); + collection.add(ent); + collection.add(ent); + when(world.getNearbyEntities(any())).thenReturn(collection); + + ell = new EntityLimitListener(addon); + } + + /** + * Test for {@link EntityLimitListener#atLimit(Island, Entity)} + */ + @Test + public void testAtLimitUnderLimit() { + AtLimitResult result = ell.atLimit(island, ent); + assertFalse(result.hit()); + } + + /** + * Test for {@link EntityLimitListener#atLimit(Island, Entity)} + */ + @Test + public void testAtLimitAtLimit() { + collection.add(ent); + AtLimitResult result = ell.atLimit(island, ent); + assertTrue(result.hit()); + assertEquals(EntityType.ENDERMAN, result.getTypelimit().getKey()); + assertEquals(Integer.valueOf(5), result.getTypelimit().getValue()); + + } + + /** + * Test for {@link EntityLimitListener#atLimit(Island, Entity)} + */ + @Test + public void testAtLimitUnderLimitIslandLimit() { + ibc.setEntityLimit(EntityType.ENDERMAN, 6); + AtLimitResult result = ell.atLimit(island, ent); + assertFalse(result.hit()); + } + + /** + * Test for {@link EntityLimitListener#atLimit(Island, Entity)} + */ + @Test + public void testAtLimitAtLimitIslandLimitNotAtLimit() { + ibc.setEntityLimit(EntityType.ENDERMAN, 6); + collection.add(ent); + AtLimitResult result = ell.atLimit(island, ent); + assertFalse(result.hit()); + } + + /** + * Test for {@link EntityLimitListener#atLimit(Island, Entity)} + */ + @Test + public void testAtLimitAtLimitIslandLimit() { + ibc.setEntityLimit(EntityType.ENDERMAN, 6); + collection.add(ent); + collection.add(ent); + AtLimitResult result = ell.atLimit(island, ent); + assertTrue(result.hit()); + assertEquals(EntityType.ENDERMAN, result.getTypelimit().getKey()); + assertEquals(Integer.valueOf(6), result.getTypelimit().getValue()); + + } + + +} diff --git a/src/test/java/world/bentobox/limits/mocks/ServerMocks.java b/src/test/java/world/bentobox/limits/mocks/ServerMocks.java index fbca488..035519f 100644 --- a/src/test/java/world/bentobox/limits/mocks/ServerMocks.java +++ b/src/test/java/world/bentobox/limits/mocks/ServerMocks.java @@ -1,118 +1,118 @@ -package world.bentobox.limits.mocks; - -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.Server; -import org.bukkit.Tag; -import org.bukkit.UnsafeValues; -import org.eclipse.jdt.annotation.NonNull; - -public final class ServerMocks { - - public static @NonNull Server newServer() { - Server mock = mock(Server.class); - - Logger noOp = mock(Logger.class); - when(mock.getLogger()).thenReturn(noOp); - when(mock.isPrimaryThread()).thenReturn(true); - - // Unsafe - UnsafeValues unsafe = mock(UnsafeValues.class); - when(mock.getUnsafe()).thenReturn(unsafe); - - // Server must be available before tags can be mocked. - Bukkit.setServer(mock); - - // Bukkit has a lot of static constants referencing registry values. To initialize those, the - // registries must be able to be fetched before the classes are touched. - Map, Object> registers = new HashMap<>(); - - doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> { - Registry registry = mock(Registry.class); - Map cache = new HashMap<>(); - doAnswer(invocationGetEntry -> { - NamespacedKey key = invocationGetEntry.getArgument(0); - // Some classes (like BlockType and ItemType) have extra generics that will be - // erased during runtime calls. To ensure accurate typing, grab the constant's field. - // This approach also allows us to return null for unsupported keys. - Class constantClazz; - try { - //noinspection unchecked - constantClazz = (Class) clazz - .getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType(); - } catch (ClassCastException e) { - throw new RuntimeException(e); - } catch (NoSuchFieldException e) { - return null; - } - - return cache.computeIfAbsent(key, key1 -> { - Keyed keyed = mock(constantClazz); - doReturn(key).when(keyed).getKey(); - return keyed; - }); - }).when(registry).get(notNull()); - return registry; - })).when(mock).getRegistry(notNull()); - - // Tags are dependent on registries, but use a different method. - // This will set up blank tags for each constant; all that needs to be done to render them - // functional is to re-mock Tag#getValues. - doAnswer(invocationGetTag -> { - Tag tag = mock(Tag.class); - doReturn(invocationGetTag.getArgument(1)).when(tag).getKey(); - doReturn(Set.of()).when(tag).getValues(); - doAnswer(invocationIsTagged -> { - Keyed keyed = invocationIsTagged.getArgument(0); - Class type = invocationGetTag.getArgument(2); - if (!type.isAssignableFrom(keyed.getClass())) { - return null; - } - // Since these are mocks, the exact instance might not be equal. Consider equal keys equal. - return tag.getValues().contains(keyed) - || tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey())); - }).when(tag).isTagged(notNull()); - return tag; - }).when(mock).getTag(notNull(), notNull(), notNull()); - - // Once the server is all set up, touch BlockType and ItemType to initialize. - // This prevents issues when trying to access dependent methods from a Material constant. - try { - Class.forName("org.bukkit.inventory.ItemType"); - Class.forName("org.bukkit.block.BlockType"); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - - return mock; - } - - public static void unsetBukkitServer() { - try { - Field server = Bukkit.class.getDeclaredField("server"); - server.setAccessible(true); - server.set(null, null); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private ServerMocks() { - } - +package world.bentobox.limits.mocks; + +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Server; +import org.bukkit.Tag; +import org.bukkit.UnsafeValues; +import org.eclipse.jdt.annotation.NonNull; + +public final class ServerMocks { + + public static @NonNull Server newServer() { + Server mock = mock(Server.class); + + Logger noOp = mock(Logger.class); + when(mock.getLogger()).thenReturn(noOp); + when(mock.isPrimaryThread()).thenReturn(true); + + // Unsafe + UnsafeValues unsafe = mock(UnsafeValues.class); + when(mock.getUnsafe()).thenReturn(unsafe); + + // Server must be available before tags can be mocked. + Bukkit.setServer(mock); + + // Bukkit has a lot of static constants referencing registry values. To initialize those, the + // registries must be able to be fetched before the classes are touched. + Map, Object> registers = new HashMap<>(); + + doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> { + Registry registry = mock(Registry.class); + Map cache = new HashMap<>(); + doAnswer(invocationGetEntry -> { + NamespacedKey key = invocationGetEntry.getArgument(0); + // Some classes (like BlockType and ItemType) have extra generics that will be + // erased during runtime calls. To ensure accurate typing, grab the constant's field. + // This approach also allows us to return null for unsupported keys. + Class constantClazz; + try { + //noinspection unchecked + constantClazz = (Class) clazz + .getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType(); + } catch (ClassCastException e) { + throw new RuntimeException(e); + } catch (NoSuchFieldException e) { + return null; + } + + return cache.computeIfAbsent(key, key1 -> { + Keyed keyed = mock(constantClazz); + doReturn(key).when(keyed).getKey(); + return keyed; + }); + }).when(registry).get(notNull()); + return registry; + })).when(mock).getRegistry(notNull()); + + // Tags are dependent on registries, but use a different method. + // This will set up blank tags for each constant; all that needs to be done to render them + // functional is to re-mock Tag#getValues. + doAnswer(invocationGetTag -> { + Tag tag = mock(Tag.class); + doReturn(invocationGetTag.getArgument(1)).when(tag).getKey(); + doReturn(Set.of()).when(tag).getValues(); + doAnswer(invocationIsTagged -> { + Keyed keyed = invocationIsTagged.getArgument(0); + Class type = invocationGetTag.getArgument(2); + if (!type.isAssignableFrom(keyed.getClass())) { + return null; + } + // Since these are mocks, the exact instance might not be equal. Consider equal keys equal. + return tag.getValues().contains(keyed) + || tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey())); + }).when(tag).isTagged(notNull()); + return tag; + }).when(mock).getTag(notNull(), notNull(), notNull()); + + // Once the server is all set up, touch BlockType and ItemType to initialize. + // This prevents issues when trying to access dependent methods from a Material constant. + try { + Class.forName("org.bukkit.inventory.ItemType"); + Class.forName("org.bukkit.block.BlockType"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return mock; + } + + public static void unsetBukkitServer() { + try { + Field server = Bukkit.class.getDeclaredField("server"); + server.setAccessible(true); + server.set(null, null); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private ServerMocks() { + } + } \ No newline at end of file