Compare commits

...

239 Commits

Author SHA1 Message Date
tastybento 3a42692909 Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2024-04-04 09:02:53 -07:00
tastybento 894fc1347c Fix test 2024-04-04 09:02:45 -07:00
tastybento e53f11ea19
Merge pull request #182 from BentoBoxWorld/gitlocalize-28605
Update Spanish locale
2024-04-04 08:55:39 -07:00
tastybento f408d9c00b Update to use BentoBox 2.3.0 2024-04-04 08:55:12 -07:00
Santos f9ce3ede64 Translate es.yml via GitLocalize 2024-03-06 08:43:37 +00:00
mt-gitlocalize 2f01344068 Translate es.yml via GitLocalize 2024-03-06 08:43:37 +00:00
BONNe 7da342bec5 Translate es.yml via GitLocalize 2024-03-06 08:43:36 +00:00
Jaimexo 65c7e6e8d0 Translate es.yml via GitLocalize 2024-03-06 08:43:35 +00:00
tastybento ee577e210a Update to 1.20.4 2024-01-13 08:54:29 -08:00
tastybento 9ffe0c740a Refactor code to enable golem spawning. #180 2024-01-09 17:47:24 +09:00
tastybento 843aa89f6c Version 1.20.1 2024-01-09 12:01:00 +09:00
tastybento 39c630b92a Version 1.20.0
This requires BentoBox 2.0.0 to run.
2023-11-28 16:04:07 -08:00
tastybento 4323a1a53a Update to BentoBox 2.0.0 API
Check for multiple islands when the player joins.
2023-11-28 16:03:11 -08:00
tastybento 2b9b3dacf8 Add NPE protection #178 2023-11-26 08:32:30 -08:00
tastybento 2d92663cd4 BentoBox API 2.0.0 2023-11-12 13:24:15 -08:00
tastybento a35816cc80
Update pom.xml 2023-07-10 21:40:28 -07:00
tastybento 9763dc1227 Update Jacoco 2023-07-10 21:24:10 -07:00
tastybento f6b3e3b8db Added distribution required for Github Action 2023-06-24 13:56:29 -07:00
tastybento 5137c24c91 Update Github Build script 2023-06-24 13:46:08 -07:00
tastybento e16d1f1893 Java 17 for GitHub action 2023-04-15 15:44:18 -07:00
tastybento 6b770d083e Fix tests. Use world#nearbyEntities method. 2023-04-15 12:15:14 -07:00
tastybento eeafa239c4 Avoid using NumberUtils. Update dependencies. 2023-04-15 11:44:56 -07:00
tastybento 68f696a46c
Merge pull request #166 from emmanuelvlad/new-placeholders 2023-04-10 06:52:59 -07:00
BONNe 995ac6191f
Merge pull request #175 from BentoBoxWorld/return-to-plugin
Return to plugin.yml
2023-04-08 19:17:22 +03:00
BONNe d87d194c65
Update LimitsPladdon.java 2023-04-08 19:16:52 +03:00
BONNe 8dd35fabcf
Update pom.xml 2023-04-08 19:16:35 +03:00
BONNe e17245b86f
Create plugin.yml 2023-04-08 19:16:00 +03:00
tastybento f0727586a3 Update annotations 2023-03-25 09:54:26 -07:00
tastybento 69ded8fc3e Add [gamemode].mod.bypass to list of perms in addon.yml 2023-03-18 17:37:49 -04:00
tastybento 9e7a52c4d9
Merge pull request #172 from piotrskibapl/develop
Disable villagers breeding when limit is reached
2023-02-12 10:47:13 -08:00
tastybento 3b6e912bae
Add ${argLine} to get jacoco coverage 2023-02-09 15:20:32 -08:00
evlad 97006d28c1 huge performance boost for entity limit 2022-11-07 02:05:09 +01:00
Piotr Skiba 989ebcba7b disable villagers breeding when limit is reached 2022-09-28 00:08:05 +02:00
BONNe a1c75e1192
Merge pull request #171 from BentoBoxWorld/gitlocalize-20576
Update Polish translation
2022-08-27 15:14:52 +03:00
wiktorm12 fdd827a297 Translate pl.yml via GitLocalize 2022-08-27 12:13:10 +00:00
BONNe bc905adccb
Replace ' with " in FR locale
Some text strings inside fr contained '. It resulted in a locale crash. This should fix that.
2022-07-07 12:05:46 +03:00
BONNe ed2af07f07
Merge pull request #167 from organizatsiya/patch-1
Create fr.yml
2022-07-06 14:41:38 +03:00
evlad f726492eae add new placeholders and BlockGrowEvent 2022-07-05 19:35:00 +02:00
organizatsiya 26d357fd42
Create fr.yml 2022-07-05 19:10:59 +02:00
BONNe cf5be3ef30
Merge pull request #163 from BentoBoxWorld/offset-command
Implement Limits "offset" command.
2022-03-28 19:06:19 +03:00
BONNe be3554bf8c Implement Limits "offset" command.
Offset command have:
- set <player> <material|entity> <number> - sets the specific limit offset for material or entity.
- add <player> <material|entity> <number> - adds the specific limit offset for material or entity.
- remove <player> <material|entity> <number> - removes the specific limit offset for material or entity.
- reset <player> <material|entity> - resets limit offset for material or entity.
- view <player> <material|entity> - views the specific limit offset for material or entity.

Relates to the #149
2022-03-28 19:04:15 +03:00
BONNe 782150f0f4
Fixes offset duplication in Limits Panel 2022-03-27 20:12:15 +03:00
BONNe 388f973aee
Merge pull request #161 from BentoBoxWorld/placeholder-fix
Fixes wrong placeholder value #159
2022-03-27 14:45:56 +03:00
BONNe 5fd461ddea
Add limits offsets to the GUI and Placeholder
Offsets were not added to the GUI and Placeholder value. Part of #159
2022-03-27 14:45:43 +03:00
BONNe b459f8bdb5
Fixes wrong placeholder value #159
Placeholder value did not check default and world limits. It used just island limits that are assigned via permissions.

This commit should fix it.
2022-03-27 14:43:31 +03:00
BONNe baa73b76a5
Merge pull request #160 from YellowZaki/develop
Turned into a Pladdon
2022-03-25 21:18:06 +02:00
YellowZaki d7bff1df7d Turned into a Pladdon 2022-03-25 17:29:27 +01:00
tastybento 1a7d2b8f3e
Merge pull request #156 from YellowZaki/develop
Fixes Villager limit
2022-01-21 18:55:21 -08:00
tastybento e8dffc76f5 Avoid NPE with ibc
https://github.com/BentoBoxWorld/Limits/issues/157
2022-01-21 18:53:45 -08:00
Alberto fe0e935e14
Fixes Villager limit 2022-01-09 20:50:34 +01:00
tastybento 360ba0b2de Rework how offsets are used.
https://github.com/BentoBoxWorld/Upgrades/issues/40
https://github.com/BentoBoxWorld/Limits/issues/117
2022-01-02 16:31:02 -08:00
tastybento d038269f8c Add getIsland method 2022-01-02 12:45:16 -08:00
tastybento 78e02b6196 Ignore groups if they are unknown. 2022-01-02 11:55:10 -08:00
tastybento 7582aa93d7 Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2022-01-02 11:16:38 -08:00
tastybento 886c6c5fa5 Remove debug 2022-01-02 11:16:27 -08:00
tastybento 8d56a4078a Version 1.19.1 2022-01-02 11:16:18 -08:00
tastybento 6e9d83c348
Merge pull request #154 from katorly/patch-1 2021-12-31 08:11:24 -08:00
Katorly f635c07ead
Update zh-CN translation for Limits 2021-12-31 19:15:02 +08:00
tastybento d8c8102010 Added locale text
https://github.com/BentoBoxWorld/Limits/issues/123
2021-12-26 16:17:14 -08:00
tastybento 343d7bbbde Improved recount code. Should be accurate.
This uses the latest approach done by Level addon. This does not yet
include entity recounting!

https://github.com/BentoBoxWorld/Limits/issues/123
2021-12-26 15:52:56 -08:00
tastybento 4cff598aee Fix bugs in map keys and groups. Fix code smells. 2021-12-25 09:10:53 -08:00
tastybento 51408751a6 Code clean up. Java updates. 2021-12-22 14:37:10 -08:00
tastybento 0248c398f6 Enable Op or mod override for breeding. 2021-12-22 13:43:45 -08:00
tastybento 38aa99b2c7 Remove debug 2021-12-22 12:18:16 -08:00
tastybento d6f12c29dc Prevent breeding using breeding event 2021-12-22 12:16:02 -08:00
tastybento 3f1465d6ef Fix NPEs 2021-12-22 10:11:59 -08:00
tastybento 14c38869df WIP try to use EntityBreedEvent to track breeding 2021-12-22 09:19:00 -08:00
tastybento 328a1a13f7 Added option to ignore the island center block from limits.
Should help with AOneBlock usage.
2021-10-17 14:48:35 -07:00
tastybento 945aae0d8b Update to BentoBox 1.18 API 2021-10-01 17:58:31 -07:00
tastybento b71700895e prevent NPE for blockCounts. 2021-09-11 07:45:09 -07:00
tastybento 390814bedb WIP fix some bugs - still more to fix. 2021-09-06 19:32:29 -07:00
tastybento 05a8a0ebbe Fix tests 2021-09-06 17:14:06 -07:00
tastybento 76c582ba1d Added API to enable offset for limits. 2021-09-06 17:11:02 -07:00
tastybento ad1f060d76 Version 1.18.0 2021-09-06 17:06:18 -07:00
tastybento be2c6480b1 Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2021-08-20 17:01:43 -07:00
tastybento db233b5ec5
Merge pull request #138 from HSGamer/patch-1
Add Vietnamese
2021-08-10 22:30:16 -07:00
Huynh Tien f7c44c7c21
Add Vietnamese 2021-08-10 20:05:21 +07:00
tastybento ebb3acd599
Merge pull request #136 from JasonLiu2002/block-placeholders
Block placeholders
2021-08-01 17:21:36 -07:00
JasonLiu2002 f94a4ac0bb Registers all blocks as placeholders 2021-07-31 16:30:41 -07:00
JasonLiu2002 0a193ebd27 Simplified how to register placeholders, changed default getBlockCount to 0 instead of -1 2021-07-30 02:16:28 -07:00
JasonLiu2002 21986d3f2d Added chest/hopper placeholders, refactored getBlockCounts to be consistent, implemented getBlockCount 2021-07-29 01:38:45 -07:00
tastybento f7d379f42d Version 1.17.2 2021-07-24 14:16:23 -07:00
tastybento 4c5fdd6390 Remove async around entity call
https://github.com/BentoBoxWorld/Limits/issues/135
2021-07-24 14:13:44 -07:00
tastybento b16332da30 Version 1.17.1 2021-07-10 18:48:35 -07:00
tastybento d0d950818d Use BentoBox 1.17.0 2021-06-27 17:10:46 -07:00
tastybento 62caf1aa47 Require 1.17 BentoBox 2021-06-20 11:12:58 -07:00
tastybento 7a04c8af41 Fix maven surefire 2021-06-20 10:59:30 -07:00
tastybento c7b310ffd8 Version 1.17.0 2021-06-20 10:54:12 -07:00
tastybento 0992a4fc52 Java 16 and Sonar Cloud 2021-06-20 10:52:48 -07:00
tastybento 6a2768e572 Adds setting option for async golum checking
https://github.com/BentoBoxWorld/Limits/issues/127
2021-05-03 16:22:42 -07:00
tastybento 0ae58db8a9 Use correct perm check perm prefix.
https://github.com/BentoBoxWorld/Limits/issues/126
2021-03-27 08:39:12 -07:00
tastybento f525ae80a5 Recalculate limits every time Limits GUI is shown. 2021-03-07 09:20:28 -08:00
tastybento e7d6d799f1 Version 1.15.6 2021-03-07 09:20:01 -08:00
tastybento d3bd125603 Version 1.15.5 2021-02-21 19:04:04 -08:00
tastybento dc4efb2a0c Entity limiting was happening in BentoBox worlds not specified in config 2021-02-21 19:02:50 -08:00
tastybento a730daf6a8 Version 1.15.4 2021-01-24 14:18:58 -08:00
tastybento 5e9a4326ba Only save if data has changed on shutdown. 2021-01-22 11:56:42 -08:00
tastybento f353de9cc8 Version 1.15.3 2021-01-22 11:56:21 -08:00
tastybento c286a65803 Use BentoBox 1.15.4 2020-12-30 22:58:24 -08:00
tastybento 8ce7c3e631 Updated to BentoBox 1.15.4 API 2020-12-27 11:11:54 -08:00
tastybento 514f2158cc Remove Wither from async checking
The fake wither explosion was not convincing enough...

If we ever get an API to set the summoned wither state then this could
be put back in.
2020-12-22 09:40:09 -08:00
tastybento 4f994b3883 Version 1.15.1 2020-12-22 09:39:17 -08:00
tastybento 368443a413
Merge pull request #115 from Baterka/develop
Fixed https://github.com/BentoBoxWorld/Limits/issues/114
2020-11-26 17:05:58 -08:00
Baterka 90b5a65c62 Fixed https://github.com/BentoBoxWorld/Limits/issues/114 2020-11-24 15:43:37 +01:00
tastybento fbf13bd99e
Merge pull request #112 from BentoBoxWorld/perm-event
Added new event that fires for each perm
2020-11-14 12:13:34 -08:00
tastybento dee2367ad6 Use JDK11 only. 2020-11-14 12:10:43 -08:00
tastybento 81e4dbefc5 Remove duplication of code for event 2020-11-14 12:07:42 -08:00
tastybento 19565e90ff Added null checks 2020-11-14 12:02:48 -08:00
tastybento c0c9e16b6c Added new event that fires for each perm
https://github.com/BentoBoxWorld/Limits/issues/111
2020-11-14 11:03:13 -08:00
tastybento b9870a4fef
Merge pull request #109 from YellowZaki/develop
Count PISTON_HEAD and MOVING_PISTON
2020-10-28 10:52:16 -07:00
YellowZaki 0f7f61c179 Count PISTON_HEAD and MOVING_PISTON 2020-10-28 14:25:36 +01:00
YellowZaki 88879f5638 Count PISTON_HEAD and MOVING_PISTON 2020-10-28 14:25:23 +01:00
tastybento eb3efc03b4
Merge pull request #107 from BentoBoxWorld/gitlocalize-13274
Add Romanian translation
2020-10-02 14:43:47 -07:00
Adi bc6c7610eb Translate ro.yml via GitLocalize 2020-10-02 11:18:53 +00:00
tastybento 4f92611df0 Check for specific types and not classes
https://github.com/BentoBoxWorld/Limits/issues/105
2020-09-30 14:46:07 -07:00
tastybento d0e7ec0d68 Adds tests for EntityLimitListener 2020-09-27 16:54:00 -07:00
tastybento 062574f0c5
Merge pull request #104 from weaondara/fixentitygroups
fix entity group limits not showing properly in limit panel
2020-09-20 09:11:25 -07:00
tastybento 3b367407ae Fixes uncounting of blocks used by wither, golem or snowman making
https://github.com/BentoBoxWorld/Limits/issues/103
2020-09-20 09:07:35 -07:00
wea_ondara 6c097468ff fix entity group limits not showing properly in limit panel 2020-09-19 18:57:23 +02:00
tastybento 0c107f2bab Temporarily not do async checked for entity spawns
https://github.com/BentoBoxWorld/Limits/issues/98
https://github.com/BentoBoxWorld/Limits/issues/100
2020-09-17 18:54:02 -07:00
tastybento 33d16d16e2 Add special handling for armor stands for jetsminions
https://github.com/BentoBoxWorld/Limits/issues/99
2020-09-12 07:42:46 -07:00
tastybento 1870a309f7 Handles golem, wither and snowman orientations
https://github.com/BentoBoxWorld/Limits/issues/97
2020-09-11 18:43:36 -07:00
tastybento 07101c72dc Possible fix for dupe exploits
https://github.com/BentoBoxWorld/Limits/issues/97
2020-09-10 19:19:49 -07:00
tastybento b8682960cf Improves the Limit Panel with sorting and pages. 2020-09-07 15:05:51 -07:00
tastybento d3a5c23cf0 Reworked entity limits to avoid glitching.
https://github.com/BentoBoxWorld/Limits/issues/95
2020-09-06 18:30:05 -07:00
tastybento 2cac6dd3f9
Merge pull request #94 from patrykstepien/patch-1
Fixed typo in config.yml
2020-08-25 18:07:07 -07:00
patrykstepien 336ede6d53
Update config.yml
Fixed typo
2020-08-25 20:33:39 +02:00
tastybento 71018c08d4
Merge pull request #88 from BentoBoxWorld/profiler
Async limit checking
2020-07-26 14:41:52 -07:00
tastybento b35a738557 1.15.0 2020-07-26 14:38:31 -07:00
tastybento 989143d218 Removed code smells. 2020-07-26 14:35:05 -07:00
tastybento 088f853f0a Removed debug 2020-07-26 14:24:03 -07:00
tastybento 64b25b0ca6 Fixed bug where world entities were counted. 2020-07-25 08:15:55 -07:00
tastybento 2da76cddd8 1.14.0 BentoBox API 2020-07-07 18:42:24 -07:00
tastybento a316eaabc6 Added type chevron 2020-06-26 16:23:16 -07:00
tastybento b9ea6ecddd Added type chevrons 2020-06-26 16:22:18 -07:00
tastybento 598ccf383c Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2020-06-26 16:19:52 -07:00
tastybento 503b4827a9 Version for 1.16.1 Minecraft 2020-06-26 16:19:42 -07:00
tastybento e86230915a
Merge pull request #76 from aurorasmiles/develop
Add EntityGroups to Limits
2020-06-21 19:25:09 -07:00
Aurora dafc710c48
move entities from Name to description to add a newline after 5 entries 2020-06-21 19:50:46 +02:00
Aurora d2b1a0527d
merge with upstream 2020-06-21 18:23:08 +02:00
tastybento 4ca3455d5b Update to BentoBox 1.14 API
Shorter table names for SQL DBs. Async db saving.
2020-06-10 20:58:07 -07:00
tastybento 2329e2c61e Fix tests 2020-06-04 07:59:24 -07:00
tastybento ad186958cc Adds event that fires when player joins and before perm check
Cancel the event to prevent perm settings of limits.
2020-06-02 17:54:54 -07:00
tastybento caf766403c Version 1.13.0 2020-06-02 17:27:33 -07:00
tastybento 8a01cd3976
Merge pull request #82 from BentoBoxWorld/gitlocalize-11967
A few spelling errors have been fixed.
2020-05-31 12:01:31 -07:00
Over_Brave e11d6825f3 Translate tr.yml via GitLocalize 2020-05-31 17:55:53 +00:00
tastybento 3d0e1b0922 Recount will count Nether and End blocks.
Fixes https://github.com/BentoBoxWorld/Limits/issues/80
2020-05-05 16:56:22 -07:00
tastybento eb065d321e Version 1.12.2 2020-05-05 16:55:44 -07:00
tastybento b58859ae3f Fixes bug where permission limits were given to everyone
Fixes https://github.com/BentoBoxWorld/Limits/issues/79
2020-04-30 16:30:34 -07:00
tastybento 100f5ea026 Version 1.12.1 2020-04-30 16:29:45 -07:00
tastybento 2d88c73559
Merge pull request #77 from workonfire/develop
Add Polish translation
2020-04-20 07:36:01 -07:00
Krystian 432214485f
Add Polish translation 2020-04-20 01:28:08 +02:00
wea_ondara 54cb8b3a10 fix limits for simple entity limit 2020-04-12 15:00:09 +02:00
wea_ondara cc6781bf44 fix group limits when limit is -1 2020-04-12 15:00:03 +02:00
wea_ondara 346f2b8ed8 integrate group limits into limit messages in entity listener 2020-04-12 14:59:55 +02:00
wea_ondara f25c027a2b adjust test cases to previous changes 2020-04-12 14:55:35 +02:00
wea_ondara f77c48218a added entity group limits via permissions to islands 2020-04-12 14:55:30 +02:00
wea_ondara 625bbe3a9f show limits in limit command 2020-04-12 14:55:26 +02:00
wea_ondara e0c208e049 added entity group limit settings 2020-04-12 14:55:21 +02:00
Florian CUNY 16974ad5ed Updated Spigot and BentoBox dependencies to latest versions 2020-04-10 17:30:36 +02:00
Florian CUNY 6abf92584b Version up 1.12.0 2020-04-10 17:30:08 +02:00
tastybento 086d251d3f Changed to use notify instead of sendmessage to avoid spam
https://github.com/BentoBoxWorld/Limits/issues/75
2020-03-28 11:15:29 -07:00
tastybento e9784b508c Version 1.9.5 2020-03-28 11:15:02 -07:00
tastybento f8ffc9bcd0 Version 1.9.4 2020-03-28 11:06:28 -07:00
tastybento 4cb061d594 Avoids counting entities if the type spawned is not limited.
https://github.com/BentoBoxWorld/Limits/issues/74
2020-03-25 21:00:37 -07:00
tastybento 55bb288dad Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2020-03-18 08:36:50 -07:00
tastybento 39f527777d Ignore negative permissions.
Fixes https://github.com/BentoBoxWorld/Limits/issues/73
2020-03-18 08:36:40 -07:00
tastybento 8e21458a7a
Updated ReadMe to show unlimitable items 2020-02-07 12:02:57 -08:00
tastybento 40a3b39748
Czech translation. Credit @Polda18 2020-01-30 10:25:14 -08:00
tastybento 1bc354e49b Adds null check for nether and end worlds
https://github.com/BentoBoxWorld/Limits/issues/64
2020-01-30 09:25:42 -08:00
tastybento 65a31c9308 Version 1.9.2 2020-01-30 09:24:16 -08:00
tastybento ea76a6deeb Clears permission limits when owner joins
Fixes https://github.com/BentoBoxWorld/Limits/issues/63
2020-01-11 09:49:15 -08:00
tastybento 8fa551ff7c Bug fixes
Fixes https://github.com/BentoBoxWorld/Limits/issues/62
2020-01-11 09:38:45 -08:00
tastybento 2b6377fee7
Merge pull request #59 from DuckSoft/patch-1
Creating zh-CN Localization
2020-01-10 16:27:16 -08:00
tastybento 36957205a4
Merge pull request #61 from BentoBoxWorld/gitlocalize-10320
Add missing translation strings.
2020-01-10 16:26:45 -08:00
tastybento 789b73d3b3 Fixed bugs with adding entity perm limits.
https://github.com/BentoBoxWorld/Limits/issues/47
2020-01-10 16:08:21 -08:00
BONNe 6538be16bf Translate lv.yml via GitLocalize 2020-01-10 14:31:31 +00:00
tastybento fb6f6757d5 Added permission limits for entities
https://github.com/BentoBoxWorld/Limits/issues/47
2020-01-09 14:42:37 -08:00
tastybento f907784bd7 Entity limits are now counted to include nether and end.
https://github.com/BentoBoxWorld/Limits/issues/43
2020-01-08 15:12:24 -08:00
DuckSoft 0bc42f224f
Creating zh-CN Localization 2020-01-02 03:42:03 +08:00
tastybento 25be7a3844 Added distribution management to POM.
https://github.com/BentoBoxWorld/website/issues/15
2019-12-16 17:05:49 -08:00
tastybento 6abd0e3588 Renamed package to world.bentobox for CI 2019-12-16 07:47:51 -08:00
tastybento 4cfbea2bee Switch to using BentoBox 1.9.0 2019-12-16 07:26:59 -08:00
Florian CUNY e6d9b721b5
Updated German translation (#55)
German translation
2019-11-30 17:24:32 +01:00
Florian CUNY ea8b4e66ae
Update de.yml 2019-11-30 17:23:38 +01:00
tastybento 12fdb63526 Translate de.yml via GitLocalize 2019-11-27 23:39:39 +00:00
FunnysBanana a18b29e7e6 Translate de.yml via GitLocalize 2019-11-27 23:39:36 +00:00
tastybento b3b81db029 Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2019-11-25 18:54:30 -08:00
tastybento 814272905a Adds support for crop trample and turtle eggs
https://github.com/BentoBoxWorld/Limits/issues/42
2019-11-25 18:07:40 -08:00
tastybento 2ba4e7fc90
Merge pull request #54 from andris155/patch-1
Translated into Hungarian
2019-11-24 13:50:38 -08:00
András Marczinkó 0e7d41d0eb
Update hu.yml 2019-11-24 11:53:08 +01:00
András Marczinkó a7e64c3358
Translated into Hungarian 2019-11-24 11:48:50 +01:00
tastybento af59047f57 Uses multi-threaded island scanning.
Optimized for Paper. Requires BentoBox 1.9.0 or later.

Makes admin calc command alias recount.

https://github.com/BentoBoxWorld/Limits/issues/52
2019-11-16 16:23:37 -08:00
tastybento d97fa9b9ae Ignore event true.
https://github.com/BentoBoxWorld/Limits/issues/51
2019-11-09 14:46:30 -08:00
tastybento 6b54fc5b8e Fix for nulls in map.
https://github.com/BentoBoxWorld/Limits/issues/50
2019-11-09 14:44:58 -08:00
tastybento fdfc080307 Removed code smells. 2019-11-08 12:35:47 -08:00
tastybento 8e56bc8c78 Make constants use immutable maps. 2019-11-08 11:27:36 -08:00
tastybento f22c5d98fa Removed reference to SONAR_TOKEN 2019-11-07 17:06:55 -08:00
tastybento b24e76ec67 Added SonarCloud 2019-11-03 18:22:19 -08:00
tastybento fc471a2bee
Merge pull request #49 from sgdc3/develop
Fix 1.13.2 support
2019-10-07 09:56:52 -07:00
Gabriele C 3f58de3d7e Fix 1.13.2 support 2019-10-05 22:05:39 +02:00
tastybento aab601ff9f
Merge pull request #46 from BONNe/patch-1
Comment out debug code
2019-09-13 16:07:36 -07:00
BONNe ad225b0dfb
Comment out debug code 2019-09-13 12:26:02 +03:00
tastybento ba71985231
Merge pull request #45 from BONNe/gitlocalize-9421
Added Latvian translation.
2019-09-12 07:38:24 -07:00
BONNe d1f0a34410 Translate lv.yml via GitLocalize 2019-09-11 11:35:46 +03:00
tastybento debcc4ccfd Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2019-09-10 19:01:47 -07:00
tastybento 06da189e26 Added recount command for players. Fixed counting for crops.
https://github.com/BentoBoxWorld/Limits/issues/44
2019-09-10 19:01:39 -07:00
tastybento 73c43a557f
Merge pull request #41 from tastybento/gitlocalize-9360
Japanese translation
2019-09-03 07:48:36 -07:00
tastybento 49c210db3f
Clean up 2019-09-03 07:48:23 -07:00
machine-translation 0a7990e6fd Translate ja.yml via GitLocalize 2019-09-03 07:47:11 -07:00
tastybento 8bf66407f6 Translate ja.yml via GitLocalize 2019-09-03 07:47:09 -07:00
tastybento 6078059e20 Added Turkish locale - credit OverBrave 2019-09-03 07:42:16 -07:00
tastybento d5bd4e5ef9 version up 1.6.1 2019-09-03 07:41:20 -07:00
tastybento ffda0cd269 1.6.0 Release 2019-08-16 19:57:33 -07:00
tastybento 4603e9e1a9 Removed dependency on EpicSpawners
https://github.com/BentoBoxWorld/Limits/issues/25
2019-08-16 19:55:58 -07:00
tastybento b0888d1e9d Move to EpicSpawners 6.0.9 API 2019-08-15 07:48:18 -07:00
tastybento 62ce0cebae Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2019-08-11 12:41:01 -07:00
tastybento 6ab40bfda6 Added build # to addon.yml 2019-08-11 12:40:54 -07:00
tastybento 078376f184 Fix for HangingPlaceEvent error
https://github.com/BentoBoxWorld/Limits/issues/36
2019-08-09 14:09:35 -07:00
tastybento 03e51fc0f8
Added build status icon 2019-08-08 07:04:27 -07:00
tastybento e5bdb80982 updates POM to use build numbers in filename 2019-08-07 21:31:38 -07:00
tastybento 2beeb118f9 Fixes locale text error
https://github.com/BentoBoxWorld/Limits/issues/33
2019-08-07 21:20:08 -07:00
tastybento 34c5e79d49 Removed old version of EpicSpawners 2019-08-02 10:06:25 -07:00
tastybento 7d920a4344 Compiled with updated EpicSpawners 6.0.7
Use mvn install:install-file -Dfile=EpicSpawners-API.jar
-DgroupId=com.songoda -DartifactId=EpicSpawners-API -Dversion=6.0.7
-Dpackaging=jar -DgeneratePom=true

and then copy into local repo.
2019-08-01 21:35:29 -07:00
tastybento 70e2030958 Fixes item frame and painting icons in limits gui
https://github.com/BentoBoxWorld/Limits/issues/32
2019-07-06 23:30:56 -07:00
tastybento a886aaac1f Added test to JoinListenerTest to fix permission setting 2019-07-04 18:07:56 -07:00
tastybento 789df98d8c Fixes issue with entities not in config being limited.
https://github.com/BentoBoxWorld/Limits/issues/30
2019-06-27 08:34:01 -07:00
tastybento 56f9a0e36e Update to EpicSpawners API 6.0.2
From https://gitlab.com/Songoda/epicspawners
2019-06-26 18:34:09 -07:00
tastybento fc577758e0 Fixes Mooshroom spawn egg in panel.
https://github.com/BentoBoxWorld/Limits/issues/28
2019-06-25 17:07:44 -07:00
tastybento c1c0537054 Merge remote-tracking branch 'origin/master' into develop
Conflicts:
	pom.xml
2019-06-22 23:46:20 -07:00
tastybento ae3d1b9ad8
Merge pull request #26 from YellowZaki/patch-5
More reasons when player is not notified for limit
2019-06-22 23:44:16 -07:00
tastybento 230aa62499 Adds support for item frames and paintings as entities
https://github.com/BentoBoxWorld/Limits/issues/24
2019-06-22 23:41:30 -07:00
YellowZaki cad0d7d722
More reasons when player is not notified for limit 2019-06-22 22:32:47 +02:00
Florian CUNY ec5123665d
added ciManagement in pom.xml 2019-06-19 09:46:24 +02:00
Florian CUNY 7dd51c141a
Updated pom.xml to the new repository name 2019-06-19 09:42:05 +02:00
tastybento e5f900a193
Merge pull request #23 from YellowZaki/patch-3
Personalize messages differencing between block an entities
2019-06-16 14:39:44 -07:00
YellowZaki bd9db6245f
limits.hit-limit -> block-limits.hit-limit 2019-06-16 14:35:10 +02:00
YellowZaki e1e84a12cc
Added entity-hit-limit 2019-06-16 14:33:38 +02:00
YellowZaki f2aa22123c
Personalize messages differencing between block an entities 2019-06-16 14:30:27 +02:00
54 changed files with 5493 additions and 1366 deletions

View File

@ -1,33 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
**Description**
A clear and concise description of what the bug is.
**Steps to reproduce the behavior:**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server Information:**
[Please complete the following information:]
- Database being used (YAML, JSON, MySQL, MongoDB): []
- OS: [e.g. iOS]
- Java Version: [e.g. Java 8]
- BentoBox version: [e.g. 1.7.2.21]
- Addons installed? [Do '/bentobox version' and copy/paste from the console]
- Other plugins? [Do '/plugins' and copy/paste from the console]
**Additional context**
Add any other context about the problem here.

View File

@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

38
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Build
on:
push:
branches:
- develop
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar

87
.gitignore vendored Normal file
View File

@ -0,0 +1,87 @@
# Git
*.orig
!.gitignore
# Windows
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.lnk
# Linux
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
# MacOS
.DS_Store
.AppleDouble
.LSOverride
._*
# Java
*.class
*.log
*.ctxt
.mtj.tmp/
*.jar
*.war
*.nar
*.ear
hs_err_pid*
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
# Intellij
*.iml
*.java___jb_tmp___
.idea/*
*.ipr
*.iws
/out/
.idea_modules/
# Eclipse
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.project
.externalToolBuilders/
*.launch
.cproject
.classpath
.buildpath
.target
# NetBeans
nbproject/private/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
nb-configuration.xml
.nb-gradle/
/.idea/

View File

@ -1,4 +1,6 @@
# 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.
@ -54,4 +56,28 @@ Usage permissions are (put the gamemode name, e.g. acidisland at the front):
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

482
pom.xml
View File

@ -1,202 +1,296 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>world.bentobox</groupId>
<artifactId>limits</artifactId>
<version>0.2.2</version>
<name>addon-limits</name>
<description>An add-on for BentoBox that limits blocks and entities on islands.</description>
<url>https://github.com/BentoBoxWorld/addon-level</url>
<inceptionYear>2018</inceptionYear>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>world.bentobox</groupId>
<artifactId>limits</artifactId>
<name>Limits</name>
<version>${revision}</version>
<scm>
<connection>scm:git:https://github.com/BentoBoxWorld/addon-limits.git</connection>
<developerConnection>scm:git:git@github.com:BentoBoxWorld/addon-limits.git</developerConnection>
<url>https://github.com/BentoBoxWorld/addon-limits</url>
</scm>
<description>An add-on for BentoBox that limits blocks and entities on islands.</description>
<url>https://github.com/BentoBoxWorld/Limits</url>
<inceptionYear>2018</inceptionYear>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/BentoBoxWorld/addon-limits/issues</url>
</issueManagement>
<developers>
<developer>
<id>tastybento</id>
<email>tastybento@bentobox.world</email>
<timezone>-8</timezone>
<roles>
<role>Lead Developer</role>
</roles>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<powermock.version>1.7.4</powermock.version>
</properties>
<scm>
<connection>scm:git:https://github.com/BentoBoxWorld/Limits.git</connection>
<developerConnection>scm:git:git@github.com:BentoBoxWorld/Limits.git</developerConnection>
<url>https://github.com/BentoBoxWorld/Limits</url>
</scm>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
</repository>
<repository>
<id>codemc</id>
<url>https://repo.codemc.org/repository/maven-snapshots/</url>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
</repositories>
<ciManagement>
<system>jenkins</system>
<url>http://ci.codemc.org/job/BentoBoxWorld/job/Limits</url>
</ciManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.14.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>world.bentobox</groupId>
<artifactId>bentobox</artifactId>
<version>1.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.songoda</groupId>
<artifactId>EpicSpawners-API</artifactId>
<version>5.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/BentoBoxWorld/Limits/issues</url>
</issueManagement>
<build>
<defaultGoal>clean package</defaultGoal>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources/locales</directory>
<targetPath>./locales</targetPath>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<show>public</show>
<failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<minimizeJar>false</minimizeJar>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<configuration>
<append>true</append>
</configuration>
<executions>
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.org/repository/maven-snapshots</url>
</snapshotRepository>
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.org/repository/maven-releases</url>
</repository>
</distributionManagement>
</project>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>2.3.0-SNAPSHOT</bentobox.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.21.0</build.version>
<sonar.projectKey>BentoBoxWorld_Limits</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
<!-- Profiles will allow to automatically change build version. -->
<profiles>
<profile>
<!-- ci profile is activated if exist environment variable BUILD_NUMBER. -->
<!-- It replaces ${build.number} that is currently '-LOCAL' with
correct build number from JENKINS machine. -->
<id>ci</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<!-- Override only if necessary -->
<build.number>-b${env.BUILD_NUMBER}</build.number>
</properties>
</profile>
<profile>
<!-- Master profile is activated if exist environment variable
GIT_BRANCH and its value is origin/master. -->
<!-- It will replace 'revision' with '${build.version}' so it
removes '-SNAPSHOT' string at the end. -->
<!-- Also, as this is release build, build number can be set
to empty string. -->
<!-- This profile will be used only if exist environment variable
GIT_BRANCH with value origin/master. -->
<id>master</id>
<activation>
<property>
<name>env.GIT_BRANCH</name>
<value>origin/master</value>
</property>
</activation>
<properties>
<!-- Override only if necessary -->
<revision>${build.version}</revision>
<!-- Empties build number variable. -->
<build.number></build.number>
</properties>
</profile>
</profiles>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
</repository>
<repository>
<id>codemc</id>
<url>https://repo.codemc.org/repository/maven-snapshots/</url>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<!-- Mockito (Unit testing) -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>world.bentobox</groupId>
<artifactId>bentobox</artifactId>
<version>${bentobox.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!-- By default ${revision} is ${build.version}-SNAPSHOT -->
<!-- If GIT_BRANCH variable is set to origin/master, then it will
be only ${build.version}. -->
<!-- By default ${build.number} is -LOCAL. -->
<!-- If the BUILD_NUMBER variable is set, then it will be -b[number]. -->
<!-- If GIT_BRANCH variable is set to origin/master, then it will
be the empty string. -->
<finalName>${project.name}-${revision}${build.number}</finalName>
<defaultGoal>clean package</defaultGoal>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources/locales</directory>
<targetPath>./locales</targetPath>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>
${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
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<configuration>
<append>true</append>
<excludes>
<!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
<configuration>
<formats>
<format>XML</format>
</formats>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,127 +0,0 @@
package bentobox.addon.limits;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.World;
import bentobox.addon.limits.commands.AdminCommand;
import bentobox.addon.limits.commands.PlayerCommand;
import bentobox.addon.limits.listeners.BlockLimitsListener;
import bentobox.addon.limits.listeners.EntityLimitListener;
import bentobox.addon.limits.listeners.EpicSpawnersListener;
import bentobox.addon.limits.listeners.JoinListener;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
/**
* Addon to BentoBox that monitors and enforces limits
* @author tastybento
*
*/
public class Limits extends Addon {
private Settings settings;
private List<GameModeAddon> gameModes;
private BlockLimitsListener blockLimitListener;
@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));
log("Limits will apply to " + gm.getDescription().getName());
}
);
// Register listener
blockLimitListener = new BlockLimitsListener(this);
registerListener(blockLimitListener);
registerListener(new JoinListener(this));
registerListener(new EntityLimitListener(this));
// Register epic spawners one tick after load
Bukkit.getScheduler().runTask(getPlugin(), () -> {
if (Bukkit.getServer().getPluginManager().getPlugin("EpicSpawners") != null) {
registerListener(new EpicSpawnersListener(this));
}
});
// Done
}
/**
* @return the settings
*/
public Settings getSettings() {
return settings;
}
/**
* @return the gameModes
*/
public List<GameModeAddon> 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 name of the game mode for this world
* @param world - world
* @return game mode name or empty string if none
*/
public String getGameModePermPrefix(World world) {
return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(gm -> gm.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));
}
}

View File

@ -1,60 +0,0 @@
package bentobox.addon.limits;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType;
import bentobox.addon.limits.commands.LimitPanel;
public class Settings {
private final Map<EntityType, Integer> limits = new HashMap<>();
private final List<String> gameModes;
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)) {
EntityType type = getType(key);
if (type != null) {
if (!type.isSpawnable() || (LimitPanel.E2M.containsKey(type) && LimitPanel.E2M.get(type) == null)) {
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...");
}
}
}
addon.log("Entity limits:");
limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log);
}
private EntityType getType(String key) {
return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null);
}
/**
* @return the limits
*/
public Map<EntityType, Integer> getLimits() {
return limits;
}
/**
* @return the gameModes
*/
public List<String> getGameModes() {
return gameModes;
}
}

View File

@ -1,150 +0,0 @@
package bentobox.addon.limits.commands;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import bentobox.addon.limits.Limits;
import bentobox.addon.limits.objects.IslandBlockCount;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
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;
/**
* Shows a panel of the blocks that are limited and their status
* @author tastybento
*
*/
public class LimitPanel {
private final Limits addon;
public final static Map<EntityType, Material> E2M = new HashMap<>();
static {
E2M.put(EntityType.PIG_ZOMBIE, Material.ZOMBIE_PIGMAN_SPAWN_EGG);
E2M.put(EntityType.SNOWMAN, Material.SNOW_BLOCK);
E2M.put(EntityType.IRON_GOLEM, Material.IRON_BLOCK);
E2M.put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG);
E2M.put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL);
E2M.put(EntityType.BOAT, Material.OAK_BOAT);
E2M.put(EntityType.ARMOR_STAND, Material.ARMOR_STAND);
// Minecarts
E2M.put(EntityType.MINECART_TNT, Material.TNT_MINECART);
E2M.put(EntityType.MINECART_CHEST, Material.CHEST_MINECART);
E2M.put(EntityType.MINECART_COMMAND, Material.COMMAND_BLOCK_MINECART);
E2M.put(EntityType.MINECART_FURNACE, Material.FURNACE_MINECART);
E2M.put(EntityType.MINECART_HOPPER, Material.HOPPER_MINECART);
E2M.put(EntityType.MINECART_MOB_SPAWNER, Material.MINECART);
E2M.put(EntityType.MINECART_TNT, Material.TNT_MINECART);
// Disallowed
E2M.put(EntityType.PRIMED_TNT, null);
E2M.put(EntityType.EVOKER_FANGS, null);
E2M.put(EntityType.LLAMA_SPIT, null);
E2M.put(EntityType.DRAGON_FIREBALL, null);
E2M.put(EntityType.AREA_EFFECT_CLOUD, null);
E2M.put(EntityType.ENDER_SIGNAL, null);
E2M.put(EntityType.SMALL_FIREBALL, null);
E2M.put(EntityType.DRAGON_FIREBALL, null);
E2M.put(EntityType.FIREBALL, null);
E2M.put(EntityType.THROWN_EXP_BOTTLE, null);
E2M.put(EntityType.EXPERIENCE_ORB, null);
E2M.put(EntityType.SHULKER_BULLET, null);
E2M.put(EntityType.WITHER_SKULL, null);
E2M.put(EntityType.TRIDENT, null);
E2M.put(EntityType.ARROW, null);
E2M.put(EntityType.SPECTRAL_ARROW, null);
E2M.put(EntityType.SNOWBALL, null);
E2M.put(EntityType.EGG, null);
E2M.put(EntityType.LEASH_HITCH, null);
E2M.put(EntityType.ITEM_FRAME, null);
E2M.put(EntityType.PAINTING, null);
E2M.put(EntityType.GIANT, null);
E2M.put(EntityType.ENDER_CRYSTAL, null);
E2M.put(EntityType.ENDER_PEARL, null);
E2M.put(EntityType.ENDER_DRAGON, null);
}
/**
* @param addon - limit addon
*/
public LimitPanel(Limits addon) {
this.addon = addon;
}
public void showLimits(World world, User user, UUID target) {
PanelBuilder pb = new PanelBuilder().name(user.getTranslation(world, "limits.panel-title")).user(user);
// 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;
}
IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
Map<Material, Integer> matLimits = addon.getBlockLimitListener().getMaterialLimits(world, island.getUniqueId());
if (matLimits.isEmpty() && addon.getSettings().getLimits().isEmpty()) {
user.sendMessage("island.limits.no-limits");
return;
}
for (Entry<Material, Integer> en : matLimits.entrySet()) {
PanelItemBuilder pib = new PanelItemBuilder();
pib.name(Util.prettifyText(en.getKey().toString()));
if (en.getKey() == Material.REDSTONE_WIRE) {
pib.icon(Material.REDSTONE);
}
else {
pib.icon(en.getKey());
}
int count = ibc == null ? 0 : ibc.getBlockCount().getOrDefault(en.getKey(), 0);
String color = count >= en.getValue() ? 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(en.getValue())));
pb.item(pib.build());
}
addon.getSettings().getLimits().forEach((k,v) -> {
PanelItemBuilder pib = new PanelItemBuilder();
pib.name(Util.prettifyText(k.toString()));
Material m = Material.BARRIER;
try {
if (E2M.containsKey(k)) {
m = E2M.get(k);
} else if (k.isAlive()) {
m = Material.valueOf(k.toString() + "_SPAWN_EGG");
} else {
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)));
pb.item(pib.build());
});
pb.build();
}
private long getCount(Island island, EntityType ent) {
return island.getWorld().getEntities().stream()
.filter(e -> e.getType().equals(ent))
.filter(e -> island.inIslandSpace(e.getLocation())).count();
}
}

View File

@ -1,146 +0,0 @@
package bentobox.addon.limits.commands;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitTask;
import bentobox.addon.limits.Limits;
import bentobox.addon.limits.listeners.BlockLimitsListener;
import bentobox.addon.limits.objects.IslandBlockCount;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
/**
*
* @author YellowZaki
*/
public class LimitsCalc {
private boolean checking;
private Limits addon;
private World world;
private Island island;
private BlockLimitsListener bll;
private IslandBlockCount ibc;
private Map<Material, Integer> blockCount;
private BukkitTask task;
private User sender;
LimitsCalc(World world, BentoBox instance, UUID targetPlayer, Limits addon, User sender) {
this.checking = true;
this.addon = addon;
this.world = world;
this.island = instance.getIslands().getIsland(world, targetPlayer);
this.bll = addon.getBlockLimitListener();
this.ibc = bll.getIsland(island.getUniqueId());
blockCount = new HashMap<>();
this.sender = sender;
Set<Pair<Integer, Integer>> chunksToScan = getChunksToScan(island);
this.task = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> {
Set<ChunkSnapshot> chunkSnapshot = new HashSet<>();
if (checking) {
Iterator<Pair<Integer, Integer>> it = chunksToScan.iterator();
if (!it.hasNext()) {
// Nothing left
tidyUp();
return;
}
// Add chunk snapshots to the list
while (it.hasNext() && chunkSnapshot.size() < 200) {
Pair<Integer, Integer> pair = it.next();
if (!world.isChunkLoaded(pair.x, pair.z)) {
world.loadChunk(pair.x, pair.z);
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
world.unloadChunk(pair.x, pair.z);
} else {
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
}
it.remove();
}
// Move to next step
checking = false;
checkChunksAsync(chunkSnapshot);
}
}, 0L, 1);
}
private void checkChunksAsync(final Set<ChunkSnapshot> chunkSnapshot) {
// Run async task to scan chunks
addon.getServer().getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
for (ChunkSnapshot chunk : chunkSnapshot) {
scanChunk(chunk);
}
// Nothing happened, change state
checking = true;
});
}
private void scanChunk(ChunkSnapshot chunk) {
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 (chunk.getX() * 16 + x < island.getMinProtectedX() || chunk.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 (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) {
Material blockData = chunk.getBlockType(x, y, z);
// Air is free
if (!blockData.equals(Material.AIR)) {
checkBlock(blockData);
}
}
}
}
}
private void checkBlock(Material md) {
md = bll.fixMaterial(md);
// md is limited
if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) {
if (!blockCount.containsKey(md)) {
blockCount.put(md, 1);
} else {
blockCount.put(md, blockCount.get(md) + 1);
}
}
}
private Set<Pair<Integer, Integer>> getChunksToScan(Island island) {
Set<Pair<Integer, Integer>> chunkSnapshot = new HashSet<>();
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) {
Pair<Integer, Integer> pair = new Pair<>(world.getBlockAt(x, 0, z).getChunk().getX(), world.getBlockAt(x, 0, z).getChunk().getZ());
chunkSnapshot.add(pair);
}
}
return chunkSnapshot;
}
private void tidyUp() {
// Cancel
task.cancel();
if (ibc == null) {
ibc = new IslandBlockCount();
}
ibc.setBlockCount(blockCount);
bll.setIsland(island.getUniqueId(), ibc);
sender.sendMessage("admin.limits.calc.finished");
}
}

View File

@ -1,145 +0,0 @@
package bentobox.addon.limits.listeners;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
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.vehicle.VehicleCreateEvent;
import bentobox.addon.limits.Limits;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class EntityLimitListener implements Listener {
private final Limits addon;
/**
* 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.getPlugin().getIWM().inWorld(e.getVehicle().getWorld())) {
return;
}
if (addon.getSettings().getLimits().containsKey(e.getVehicle().getType())) {
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
if (entity instanceof Player) {
Player player = (Player)entity;
boolean bypass = (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getVehicle().getWorld()) + "mod.bypass"));
// Check island
addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation()).ifPresent(island -> {
// Ignore spawn
if (island.isSpawn()) {
return;
}
// Check if the player is at the limit
if (atLimit(island, bypass, e.getVehicle())) {
e.setCancelled(true);
for (Entity ent : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
if (ent instanceof Player) {
((Player) ent).updateInventory();
User.getInstance(ent).sendMessage("limits.hit-limit", "[material]",
Util.prettifyText(e.getVehicle().getType().toString())
,"[number]", String.valueOf(addon.getSettings().getLimits().get(e.getVehicle().getType())));
}
}
}
});
}
}
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCreatureSpawn(final CreatureSpawnEvent e) {
// Return if not in a known world
if (!addon.getPlugin().getIWM().inWorld(e.getLocation())) {
return;
}
if (!addon.getSettings().getLimits().containsKey(e.getEntityType())) {
// Unknown entity limit or unlimited
return;
}
boolean bypass = false;
// Check why it was spawned
switch (e.getSpawnReason()) {
// These reasons are due to a player being involved (usually) so there may be a bypass
case BREEDING:
case BUILD_IRONGOLEM:
case BUILD_SNOWMAN:
case BUILD_WITHER:
case CURED:
case EGG:
case SPAWNER_EGG:
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) {
if (entity instanceof Player) {
Player player = (Player)entity;
if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + "mod.bypass")) {
bypass = true;
break;
}
}
}
break;
default:
// Other natural reasons
break;
}
// Tag the entity with the island spawn location
checkLimit(e, bypass);
}
private void checkLimit(CreatureSpawnEvent e, boolean bypass) {
addon.getIslands().getIslandAt(e.getLocation()).ifPresent(island -> {
// Check if creature is allowed to spawn or not
if (!island.isSpawn() && atLimit(island, bypass, e.getEntity())) {
// Not allowed
e.setCancelled(true);
// If the reason is anything but because of a spawner then tell players within range
if (!e.getSpawnReason().equals(SpawnReason.SPAWNER)) {
for (Entity ent : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) {
if (ent instanceof Player) {
User.getInstance(ent).sendMessage("limits.hit-limit", "[material]",
Util.prettifyText(e.getEntityType().toString()),
"[number]", String.valueOf(addon.getSettings().getLimits().get(e.getEntityType())));
}
}
}
}
});
}
/**
* Checks if new entities can be added to island
* @param island - island
* @param bypass - true if this is being done by a player with authorization to bypass limits
* @param ent - the entity
* @return true if at the limit, false if not
*/
private boolean atLimit(Island island, boolean bypass, Entity ent) {
return addon.getSettings().getLimits().getOrDefault(ent.getType(), -1) <= ent.getWorld().getEntities().stream()
.filter(e -> e.getType().equals(ent.getType()))
.filter(e -> island.inIslandSpace(e.getLocation())).count();
}
}

View File

@ -1,33 +0,0 @@
package bentobox.addon.limits.listeners;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import com.songoda.epicspawners.api.events.SpawnerBreakEvent;
import bentobox.addon.limits.Limits;
/**
* @author tastybento
*
*/
public class EpicSpawnersListener implements Listener {
Limits addon;
/**
* @param addon
*/
public EpicSpawnersListener(Limits addon) {
this.addon = addon;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockBreak(SpawnerBreakEvent e) {
Block b = e.getSpawner().getLocation().getBlock();
addon.getBlockLimitListener().handleBreak(e, e.getPlayer(), b);
}
}

View File

@ -1,152 +0,0 @@
package bentobox.addon.limits.listeners;
import java.util.Locale;
import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
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 bentobox.addon.limits.Limits;
import bentobox.addon.limits.objects.IslandBlockCount;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent;
import world.bentobox.bentobox.database.objects.Island;
/**
* 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;
}
private void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) {
IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) {
int limit = -1;
if (perms.getPermission().startsWith(permissionPrefix)) {
// Get the Material
String[] split = perms.getPermission().split("\\.");
if (split.length != 5) {
logError(player.getName(), perms.getPermission(), "format must be " + permissionPrefix + "MATERIAL.NUMBER");
return;
}
Material m = Material.getMaterial(split[3].toUpperCase(Locale.ENGLISH));
if (m == null) {
logError(player.getName(), perms.getPermission(), split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material");
return;
}
// Get the max value should there be more than one
if (perms.getPermission().contains(permissionPrefix + ".*")) {
logError(player.getName(), perms.getPermission(), "wildcards are not allowed");
return;
}
if (!NumberUtils.isDigits(split[4])) {
logError(player.getName(), perms.getPermission(), "the last part MUST be a number!");
} else {
limit = Math.max(limit, Integer.valueOf(split[4]));
// Set the limit
if (ibc == null) {
ibc = new IslandBlockCount(islandId, gameMode);
}
ibc.setBlockLimit(m, limit);
}
}
}
// If any changes have been made then store it
if (ibc != null) {
addon.getBlockLimitListener().setIsland(islandId, ibc);
}
}
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 -> {
if (addon.getIslands().hasIsland(gm.getOverWorld(), e.getPlayer().getUniqueId())) {
String islandId = addon.getIslands().getIsland(gm.getOverWorld(), e.getPlayer().getUniqueId()).getUniqueId();
checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId, gm.getDescription().getName());
}
});
}
@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()) {
checkPerms(owner.getPlayer(), prefix + "island.limit.", island.getUniqueId(), name);
}
}
}
}
}

View File

@ -1,161 +0,0 @@
package bentobox.addon.limits.objects;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Material;
import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.database.objects.DataObject;
/**
* @author tastybento
*
*/
public class IslandBlockCount implements DataObject {
@Expose
private String uniqueId = "";
@Expose
private String gameMode = "";
@Expose
private Map<Material, Integer> blockCount = new HashMap<>();
/**
* Permission based limits
*/
@Expose
private Map<Material, Integer> blockLimits = new HashMap<>();
// Required for YAML database
public IslandBlockCount() {}
public IslandBlockCount(String uniqueId2, String gameMode2) {
this.uniqueId = uniqueId2;
this.gameMode = gameMode2;
}
/* (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 blockCount
*/
public Map<Material, Integer> getBlockCount() {
return blockCount;
}
/**
* @param blockCount the blockCount to set
*/
public void setBlockCount(Map<Material, Integer> blockCount) {
this.blockCount = blockCount;
}
/**
* Add a material to the count
* @param material - material
*/
public void add(Material material) {
blockCount.merge(material, 1, Integer::sum);
}
/**
* Remove a material from the count
* @param material - material
*/
public void remove(Material material) {
blockCount.put(material, blockCount.getOrDefault(material, 0) - 1);
blockCount.values().removeIf(v -> v <= 0);
}
/**
* 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 blockCount.getOrDefault(material, 0) >= limit;
}
/**
* 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 blockLimits.containsKey(m) && blockCount.getOrDefault(m, 0) >= blockLimits.get(m);
}
public boolean isBlockLimited(Material m) {
return blockLimits.containsKey(m);
}
/**
* @return the blockLimits
*/
public Map<Material, Integer> getBlockLimits() {
return blockLimits;
}
/**
* @param blockLimits the blockLimits to set
*/
public void setBlockLimits(Map<Material, Integer> blockLimits) {
this.blockLimits = blockLimits;
}
/**
* Get the block limit for this material for this island
* @param m - material
* @return limit or -1 for unlimited
*/
public Integer getBlockLimit(Material m) {
return blockLimits.getOrDefault(m, -1);
}
/**
* 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) {
blockLimits.put(m, limit);
}
/**
* @return the gameMode
*/
public String getGameMode() {
return gameMode;
}
public boolean isGameMode(String gameMode) {
return this.gameMode.equals(gameMode);
}
/**
* @param gameMode the gameMode to set
*/
public void setGameMode(String gameMode) {
this.gameMode = gameMode;
}
}

View File

@ -0,0 +1,262 @@
package world.bentobox.limits;
import java.util.Arrays;
import java.util.List;
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 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<GameModeAddon> gameModes;
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<GameModeAddon> 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 name of the game mode for this world
* @param world - world
* @return game mode name 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;
Arrays.stream(Material.values())
.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;
}
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);
}
}

View File

@ -0,0 +1,14 @@
package world.bentobox.limits;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Pladdon;
public class LimitsPladdon extends Pladdon {
@Override
public Addon getAddon() {
return new Limits();
}
}

View File

@ -0,0 +1,197 @@
package world.bentobox.limits;
import java.util.*;
import java.util.stream.Collectors;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType;
public class Settings {
private final Map<EntityType, Integer> limits = new EnumMap<>(EntityType.class);
private final Map<EntityType, List<EntityGroup>> groupLimits = new EnumMap<>(EntityType.class);
private final List<String> gameModes;
private final boolean asyncGolums;
private static final List<EntityType> DISALLOWED = Arrays.asList(
EntityType.PRIMED_TNT,
EntityType.EVOKER_FANGS,
EntityType.LLAMA_SPIT,
EntityType.DRAGON_FIREBALL,
EntityType.AREA_EFFECT_CLOUD,
EntityType.ENDER_SIGNAL,
EntityType.SMALL_FIREBALL,
EntityType.FIREBALL,
EntityType.THROWN_EXP_BOTTLE,
EntityType.EXPERIENCE_ORB,
EntityType.SHULKER_BULLET,
EntityType.WITHER_SKULL,
EntityType.TRIDENT,
EntityType.ARROW,
EntityType.SPECTRAL_ARROW,
EntityType.SNOWBALL,
EntityType.EGG,
EntityType.LEASH_HITCH,
EntityType.GIANT,
EntityType.ENDER_CRYSTAL,
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)) {
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");
Set<EntityType> 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);
entities.forEach(e -> {
List<EntityGroup> 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<EntityType, Integer> getLimits() {
return Collections.unmodifiableMap(limits);
}
/**
* @return the group limits
*/
public Map<EntityType, List<EntityGroup>> getGroupLimits() {
return groupLimits;
}
/**
* @return the group limit definitions
*/
public List<EntityGroup> getGroupLimitDefinitions() {
return groupLimits.values().stream().flatMap(Collection::stream).distinct().toList();
}
/**
* @return the gameModes
*/
public List<String> getGameModes() {
return gameModes;
}
/**
* A named class representing a group of entities and their limits
*
*/
public static class EntityGroup {
private final String name;
private final Set<EntityType> types;
private final int limit;
public EntityGroup(String name, Set<EntityType> types, int limit) {
this.name = name;
this.types = types;
this.limit = limit;
}
public boolean contains(EntityType type) {
return types.contains(type);
}
public String getName() {
return name;
}
public Set<EntityType> 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);
}
@Override
public String toString()
{
return "EntityGroup{" + "name=" + name + ", types=" + types + ", limit=" + limit + '}';
}
}
/**
* @return the asyncGolums
*/
public boolean isAsyncGolums() {
return asyncGolums;
}
}

View File

@ -0,0 +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<RecountCalculator> toProcessQueue;
private final Map<RecountCalculator, Long> 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<Results> 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<Results> addToQueue(Island island) {
CompletableFuture<Results> 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<RecountCalculator, Long> getInProcessQueue() {
return inProcessQueue;
}
/**
* @return the task
*/
protected BukkitTask getTask() {
return task;
}
}

View File

@ -0,0 +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<Pair<Integer, Integer>> chunksToCheck;
private final Island island;
private final CompletableFuture<Results> r;
private final Results results;
private final Map<Environment, World> worlds = new EnumMap<>(Environment.class);
private final List<Location> 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<Results> 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<Pair<Integer, Integer>> getChunksToScan(Island island) {
Queue<Pair<Integer, Integer>> 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<Results> 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<List<Chunk>> getWorldChunk(Environment env, Queue<Pair<Integer, Integer>> pairList) {
if (worlds.containsKey(env)) {
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>();
List<Chunk> 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<List<Chunk>> r2, World world, Queue<Pair<Integer, Integer>> pairList,
List<Chunk> chunkList) {
if (pairList.isEmpty()) {
r2.complete(chunkList);
return;
}
Pair<Integer, Integer> 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<Boolean> scanChunk(List<Chunk> chunks) {
// If the chunk hasn't been generated, return
if (chunks == null || chunks.isEmpty()) {
return CompletableFuture.completedFuture(false);
}
// Count blocks in chunk
CompletableFuture<Boolean> 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<Boolean> 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<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>();
int i = 0;
while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
pairList.add(chunksToCheck.poll());
}
Queue<Pair<Integer, Integer>> endPairList = new ConcurrentLinkedQueue<>(pairList);
Queue<Pair<Integer, Integer>> netherPairList = new ConcurrentLinkedQueue<>(pairList);
// Set up the result
CompletableFuture<Boolean> 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<Location> 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();
});
}
*/
}
}

View File

@ -0,0 +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<Material> mdCount = HashMultiset.create();
final Multiset<EntityType> entityCount = HashMultiset.create();
final Result state;
public Results(Result state) {
this.state = state;
}
public Results() {
this.state = Result.AVAILABLE;
}
/**
* @return the mdCount
*/
public Multiset<Material> getMdCount() {
return mdCount;
}
/**
* @return the state
*/
public Result getState() {
return state;
}
/**
* @return the entityCount
*/
public Multiset<EntityType> getEntityCount() {
return entityCount;
}
}

View File

@ -0,0 +1 @@
package world.bentobox.limits.calculators;

View File

@ -1,14 +1,17 @@
package bentobox.addon.limits.commands;
package world.bentobox.limits.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import bentobox.addon.limits.Limits;
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
@ -26,7 +29,9 @@ public class AdminCommand extends CompositeCommand {
public AdminCommand(Limits addon, CompositeCommand parent) {
super(parent, "limits");
this.addon = addon;
new CalcCommand(addon, this);
new CalcCommand(this.addon, this);
new OffsetCommand(this.addon, this);
}
/* (non-Javadoc)
@ -53,7 +58,7 @@ public class AdminCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", args.get(0));
return true;
} else {
new LimitPanel(addon).showLimits(getWorld(), user, playerUUID);
new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, playerUUID);
}
return true;
} else {

View File

@ -1,22 +1,25 @@
package bentobox.addon.limits.commands;
package world.bentobox.limits.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import bentobox.addon.limits.Limits;
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
* @author YellowZaki, tastybento
*/
public class CalcCommand extends CompositeCommand {
private final Limits addon;
private Island island;
/**
* Admin command
@ -24,7 +27,7 @@ public class CalcCommand extends CompositeCommand {
* @param addon - addon
*/
public CalcCommand(Limits addon, CompositeCommand parent) {
super(parent, "calc");
super(parent, "calc", "recount");
this.addon = addon;
}
@ -49,10 +52,26 @@ public class CalcCommand extends CompositeCommand {
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
calcLimits(playerUUID, user);
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);
@ -60,13 +79,7 @@ public class CalcCommand extends CompositeCommand {
}
}
public void calcLimits(UUID targetPlayer, User sender) {
if (addon.getIslands().getIsland(getWorld(), targetPlayer) != null) {
new LimitsCalc(getWorld(), getPlugin(), targetPlayer, addon, sender);
} else {
sender.sendMessage("general.errors.player-has-no-island");
}
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {

View File

@ -0,0 +1,689 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.limits.commands.admin;
import com.google.common.base.Enums;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
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<String> 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<String> 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<List<String>> tabComplete(User user, String alias, List<String> 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<String> 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<List<String>> tabComplete(User user, String alias, List<String> 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<String> 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<List<String>> tabComplete(User user, String alias, List<String> 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<String> 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<List<String>> tabComplete(User user, String alias, List<String> 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<String> 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<List<String>> tabComplete(User user, String alias, List<String> 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<List<String>> craftTabComplete(User user, String alias, List<String> 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<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
else if (args.size() == 5)
{
List<String> 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();
}
}
}

View File

@ -0,0 +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<Material, Integer> 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();
}
}

View File

@ -0,0 +1,269 @@
package world.bentobox.limits.commands.player;
import java.util.*;
import java.util.Map.Entry;
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.Limits;
import world.bentobox.limits.Settings.EntityGroup;
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<EntityType, Material> E2M = ImmutableMap.<EntityType, Material>builder()
.put(EntityType.MUSHROOM_COW, Material.MOOSHROOM_SPAWN_EGG)
.put(EntityType.SNOWMAN, 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.MINECART_TNT, Material.TNT_MINECART)
.put(EntityType.MINECART_CHEST, Material.CHEST_MINECART)
.put(EntityType.MINECART_COMMAND, Material.COMMAND_BLOCK_MINECART)
.put(EntityType.MINECART_FURNACE, Material.FURNACE_MINECART)
.put(EntityType.MINECART_HOPPER, Material.HOPPER_MINECART)
.put(EntityType.MINECART_MOB_SPAWNER, Material.MINECART)
.build();
// This is a map of blocks to Material
private static final Map<Material, Material> B2M;
static {
ImmutableMap.Builder<Material, Material> builder = ImmutableMap.<Material, Material>builder()
.put(Material.POTATOES, Material.POTATO)
.put(Material.CARROTS, Material.CARROT)
.put(Material.BEETROOTS, Material.BEETROOT)
.put(Material.REDSTONE_WIRE, Material.REDSTONE);
// 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<Material, Integer> 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<EntityGroup, Integer> groupMap = addon.getSettings().getGroupLimitDefinitions().stream().collect(Collectors.toMap(e -> e, EntityGroup::getLimit));
// Group by same loop up map
Map<String, EntityGroup> 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();
EntityType k = v.getTypes().iterator().next();
pib.name(v.getName());
String description = "";
description += "(" + prettyNames(v) + ")\n";
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, 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<EntityType, Integer> 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(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<Material, Integer> matLimits) {
// Material limits
for (Entry<Material, Integer> en : matLimits.entrySet()) {
PanelItemBuilder pib = new PanelItemBuilder();
pib.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() {
return user.getTranslation(world, "limits.panel-title") + " " + sortBy.name();
}
@Override
public List<@Nullable PanelItem> getPanelItems() {
return result;
}
@Override
public String getPermission() {
return "";
}
private String prettyNames(EntityGroup v) {
StringBuilder sb = new StringBuilder();
List<EntityType> 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;
}
}

View File

@ -1,10 +1,11 @@
package bentobox.addon.limits.commands;
package world.bentobox.limits.commands.player;
import java.util.List;
import bentobox.addon.limits.Limits;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.limits.Limits;
/**
* User command for limits
@ -22,6 +23,7 @@ public class PlayerCommand extends CompositeCommand {
public PlayerCommand(Limits addon, CompositeCommand parent) {
super(parent, "limits");
this.addon = addon;
new RecountCommand(addon, this);
}
/* (non-Javadoc)
@ -44,7 +46,7 @@ public class PlayerCommand extends CompositeCommand {
showHelp(this, user);
return false;
} else {
new LimitPanel(addon).showLimits(getWorld(), user, user.getUniqueId());
new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, user.getUniqueId());
return true;
}
}

View File

@ -0,0 +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<String> 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<String> 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;
}
}

View File

@ -0,0 +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;
}
}

View File

@ -0,0 +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.Settings.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;
}
}

View File

@ -1,44 +1,35 @@
package bentobox.addon.limits.listeners;
package world.bentobox.limits.listeners;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
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.entity.Player;
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.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.block.*;
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 bentobox.addon.limits.Limits;
import bentobox.addon.limits.objects.IslandBlockCount;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteEvent;
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
@ -50,6 +41,14 @@ public class BlockLimitsListener implements Listener {
* Blocks that are not counted
*/
private static final List<Material> DO_NOT_COUNT = Arrays.asList(Material.LAVA, Material.WATER, Material.AIR, Material.FIRE, Material.END_PORTAL, Material.NETHER_PORTAL);
private static final List<Material> STACKABLE;
static {
List<Material> 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
@ -60,7 +59,7 @@ public class BlockLimitsListener implements Listener {
private final Map<String, Integer> saveMap = new HashMap<>();
private final Database<IslandBlockCount> handler;
private final Map<World, Map<Material, Integer>> worldLimitMap = new HashMap<>();
private Map<Material, Integer> defaultLimitMap = new HashMap<>();
private Map<Material, Integer> defaultLimitMap = new EnumMap<>(Material.class);
public BlockLimitsListener(Limits addon) {
this.addon = addon;
@ -69,7 +68,7 @@ public class BlockLimitsListener implements Listener {
handler.loadObjects().forEach(ibc -> {
// Clean up
if (addon.isCoveredGameMode(ibc.getGameMode())) {
ibc.getBlockCount().keySet().removeIf(DO_NOT_COUNT::contains);
ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains);
// Store
islandCountMap.put(ibc.getUniqueId(), ibc);
} else {
@ -88,19 +87,19 @@ public class BlockLimitsListener implements Listener {
addon.log("Loading default limits");
if (addon.getConfig().isConfigurationSection("blocklimits")) {
ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
defaultLimitMap = loadLimits(limitConfig);
defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
}
// Load specific worlds
if (addon.getConfig().isConfigurationSection("worlds")) {
ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds");
for (String worldName : worlds.getKeys(false)) {
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(matsConfig));
worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig)));
}
}
}
@ -114,7 +113,7 @@ public class BlockLimitsListener implements Listener {
* @return limit map
*/
private Map<Material, Integer> loadLimits(ConfigurationSection cs) {
Map<Material, Integer> mats = new HashMap<>();
Map<Material, Integer> 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)) {
@ -132,22 +131,42 @@ public class BlockLimitsListener implements Listener {
* Save the count database completely
*/
public void save() {
islandCountMap.values().forEach(handler::saveObject);
islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync);
}
// Player-related events
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@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.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockBreakEvent e) {
handleBreak(e, e.getPlayer(), e.getBlock());
handleBreak(e, e.getBlock());
}
void handleBreak(Cancellable e, Player player, Block b) {
notify(e, User.getInstance(player), process(b, false), b.getType());
@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);
@ -166,14 +185,21 @@ public class BlockLimitsListener implements Listener {
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@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.sendMessage("limits.hit-limit",
user.notify("block-limits.hit-limit",
"[material]", Util.prettifyText(m.toString()),
TextVariables.NUMBER, String.valueOf(limit));
e.setCancelled(true);
@ -181,76 +207,107 @@ public class BlockLimitsListener implements Listener {
}
// Non-player events
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockBurnEvent e) {
process(e.getBlock(), false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockExplodeEvent e) {
e.blockList().forEach(b -> process(b, false));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockFadeEvent e) {
process(e.getBlock(), false);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockFormEvent e) {
process(e.getBlock(), true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlock(BlockGrowEvent e) {
process(e.getBlock(), true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockSpreadEvent e) {
process(e.getBlock(), true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(EntityBlockFormEvent e) {
process(e.getBlock(), true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = 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.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(EntityExplodeEvent e) {
e.blockList().forEach(b -> process(b, false));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@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.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockFromToEvent e) {
if (e.getBlock().isLiquid()) {
if (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);
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;
}
}
}
private int process(Block b, boolean add) {
return process(b, add, b.getType());
}
// It wouldn't make sense to count REDSTONE_WALL_TORCH and REDSTONE_TORCH as separed limits.
public Material fixMaterial(Material b) {
if (b == Material.REDSTONE_WALL_TORCH) {
return Material.REDSTONE_TORCH;
} else {
return b;
}
return mat;
}
/**
@ -258,11 +315,10 @@ public class BlockLimitsListener implements Listener {
*
* @param b - block
* @param add - true to add a block, false to remove
* @param changeTo - material this block will become
* @return limit amount if over limit, or -1 if no limitation
*/
private int process(Block b, boolean add, Material changeTo) {
if (DO_NOT_COUNT.contains(fixMaterial(b.getType())) || !addon.inGameModeWorld(b.getWorld())) {
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
@ -273,39 +329,54 @@ public class BlockLimitsListener implements Listener {
// 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));
saveMap.putIfAbsent(id, 0);
if (add) {
// Check limit
int limit = checkLimit(b.getWorld(), fixMaterial(b.getType()), id);
int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id);
if (limit > -1) {
return limit;
}
islandCountMap.get(id).add(fixMaterial(b.getType()));
saveMap.merge(id, 1, Integer::sum);
islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
} else {
if (islandCountMap.containsKey(id)) {
// Check for changes
if (!fixMaterial(changeTo).equals(fixMaterial(b.getType())) && fixMaterial(changeTo).isBlock() && !DO_NOT_COUNT.contains(fixMaterial(changeTo))) {
// Check limit
int limit = checkLimit(b.getWorld(), fixMaterial(changeTo), id);
if (limit > -1) {
return limit;
}
islandCountMap.get(id).add(fixMaterial(changeTo));
}
islandCountMap.get(id).remove(fixMaterial(b.getType()));
saveMap.merge(id, 1, Integer::sum);
islandCountMap.get(id).remove(fixMaterial(b.getBlockData()));
}
}
if (saveMap.get(id) > CHANGE_LIMIT) {
handler.saveObject(islandCountMap.get(id));
saveMap.remove(id);
}
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
*
@ -316,18 +387,18 @@ public class BlockLimitsListener implements Listener {
*/
private int checkLimit(World w, Material m, String id) {
// Check island limits
IslandBlockCount island = islandCountMap.get(id);
if (island.isBlockLimited(m)) {
return island.isAtLimit(m) ? island.getBlockLimit(m) : -1;
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 island.isAtLimit(m, worldLimitMap.get(w).get(m)) ? worldLimitMap.get(w).get(m) : -1;
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) && island.isAtLimit(m, defaultLimitMap.get(m))) {
return defaultLimitMap.get(m);
if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) {
return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m);
}
// No limit
return -1;
@ -342,16 +413,21 @@ public class BlockLimitsListener implements Listener {
*/
public Map<Material, Integer> getMaterialLimits(World w, String id) {
// Merge limits
Map<Material, Integer> result = new HashMap<>();
Map<Material, Integer> result = new EnumMap<>(Material.class);
// Default
defaultLimitMap.forEach(result::put);
result.putAll(defaultLimitMap);
// World
if (worldLimitMap.containsKey(w)) {
worldLimitMap.get(w).forEach(result::put);
result.putAll(worldLimitMap.get(w));
}
// Island
if (islandCountMap.containsKey(id)) {
islandCountMap.get(id).getBlockLimits().forEach(result::put);
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;
}
@ -361,7 +437,7 @@ public class BlockLimitsListener implements Listener {
*
* @param e - island delete event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIslandDelete(IslandDeleteEvent e) {
islandCountMap.remove(e.getIsland().getUniqueId());
saveMap.remove(e.getIsland().getUniqueId());
@ -378,7 +454,7 @@ public class BlockLimitsListener implements Listener {
*/
public void setIsland(String islandId, IslandBlockCount ibc) {
islandCountMap.put(islandId, ibc);
handler.saveObject(ibc);
handler.saveObjectAsync(ibc);
}
/**
@ -387,8 +463,19 @@ public class BlockLimitsListener implements Listener {
* @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()));
}
}

View File

@ -0,0 +1,472 @@
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.Villager;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
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.Limits;
import world.bentobox.limits.Settings;
import world.bentobox.limits.Settings.EntityGroup;
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<UUID> justSpawned = new ArrayList<>();
private static final List<BlockFace> 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()) {
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().equals(Material.CARVED_PUMPKIN)) {
// 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().equals(Material.CARVED_PUMPKIN)) {
// 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<Settings.EntityGroup, Integer> 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<Settings.EntityGroup> 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<String, EntityGroup> 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<Settings.EntityGroup, Integer> 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<EntityType, Integer> typelimit;
private Map.Entry<EntityGroup, Integer> 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<EntityType, Integer> getTypelimit() {
return typelimit;
}
public Map.Entry<EntityGroup, Integer> getGrouplimit() {
return grouplimit;
}
}
}

View File

@ -0,0 +1,276 @@
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.Limits;
import world.bentobox.limits.Settings.EntityGroup;
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()
.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);
}
}
}
}
}

View File

@ -1,4 +1,4 @@
package bentobox.addon.limits.objects;
package world.bentobox.limits.objects;
import java.util.HashMap;
import java.util.Map;
@ -7,11 +7,13 @@ 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
@ -78,10 +80,9 @@ public class EntityLimitsDO implements DataObject {
if (obj == null) {
return false;
}
if (!(obj instanceof EntityLimitsDO)) {
if (!(obj instanceof EntityLimitsDO other)) {
return false;
}
EntityLimitsDO other = (EntityLimitsDO) obj;
if (uniqueId == null) {
return other.uniqueId == null;
} else return uniqueId.equals(other.uniqueId);

View File

@ -0,0 +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<Material, Integer> blockCounts = new EnumMap<>(Material.class);
/**
* Permission based limits
*/
@Expose
private Map<Material, Integer> blockLimits = new EnumMap<>(Material.class);
@Expose
private Map<Material, Integer> blockLimitsOffset = new EnumMap<>(Material.class);
private boolean changed;
@Expose
private Map<String, Integer> entityGroupLimits = new HashMap<>();
@Expose
private Map<String, Integer> entityGroupLimitsOffset = new HashMap<>();
@Expose
private Map<EntityType, Integer> entityLimits = new EnumMap<>(EntityType.class);
@Expose
private Map<EntityType, Integer> 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<Material, Integer> 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<Material, Integer> getBlockLimits() {
return Objects.requireNonNullElse(blockLimits, new EnumMap<>(Material.class));
}
/**
* @return the blockLimitsOffset
*/
public Map<Material, Integer> 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<String, Integer> getEntityGroupLimits() {
return Objects.requireNonNullElse(entityGroupLimits, new HashMap<>());
}
/**
* @return the entityGroupLimitsOffset
*/
public Map<String, Integer> 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<EntityType, Integer> getEntityLimits() {
return Objects.requireNonNullElse(entityLimits, new EnumMap<>(EntityType.class));
}
/**
* @return the entityLimitsOffset
*/
public Map<EntityType, Integer> 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<Material, Integer> 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<Material, Integer> 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<String, Integer> 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<EntityType, Integer> 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();
}
}

View File

@ -1,27 +1,22 @@
name: Limits
main: bentobox.addon.limits.Limits
version: ${version}
main: world.bentobox.limits.Limits
version: ${version}${build.number}
api-version: 2.3.0
authors: tastybento
softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid
softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid
permissions:
acidisland.limits.player.limits:
'[gamemode].limits.player.limits':
description: Player can use limits command
default: true
acidisland.limits.admin.limits:
description: Player can use admin limits command
default: op
bskyblock.limits.player.limits:
description: Player can use limits command
'[gamemode].limits.player.recount':
description: Player can use recount command
default: true
bskyblock.limits.admin.limits:
'[gamemode].limits.admin.limits':
description: Player can use admin limits command
default: op
caveblock.limits.player.limits:
description: Player can use limits command
default: true
caveblock.limits.admin.limits:
description: Player can use admin limits command
'[gamemode].mod.bypass':
description: Player can bypass limits
default: op

View File

@ -2,7 +2,11 @@
gamemodes:
- AcidIsland
- BSkyBlock
- CaveBock
- 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
@ -10,6 +14,12 @@ gamemodes:
# 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.
@ -24,11 +34,10 @@ worlds:
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 entites in over world, 5 in nether and 5 in the end.
# 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
pig_ZOMbIe: 15

View File

@ -0,0 +1,35 @@
###########################################################################################
# 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 #
# #
# Translation by: CZghost #
###########################################################################################
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: "<player>"
description: "ukázat omezení ostrova hráče"
calc:
parameters: "<player>"
description: "přepočítat omezení ostrova hráče"
finished: "&aPřepočítání ostrova úspěšně dokončeno!"
island:
limits:
description: "ukázat omezení tvého ostrova"
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cTento svět nemá žádné omezení"
recount:
description: "přepočítá omezení tvého ostrova"

View File

@ -0,0 +1,25 @@
---
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: "<player>"
description: Zeige die Limitierungen für den Spieler an
calc:
parameters: "<player>"
description: Berechne die Insel Limitierungen für den Spieler neu
finished: "&aInselberechnung erfolgreich abgeschlossen!"
island:
limits:
description: Zeige deine Insel Limitierungen
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cDiese Welt hat keine Limitierungen"
recount:
description: Zählt die Limitierungen für deine Insel auf

View File

@ -3,10 +3,14 @@
# the one at http://yaml-online-parser.appspot.com #
###########################################################################################
limits:
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:
@ -15,15 +19,43 @@ admin:
calc:
parameters: "<player>"
description: "recalculate the island limits for player"
finished: "&aIsland recalc finished sucessfully!"
finished: "&a Island recalc finished successfully!"
offset:
unknown: "&c Unknown material or entity [name]."
main:
parameters: ""
description: "allows to manage limits offsets for materials and entities"
set:
parameters: "<player> <material|entity> <number>"
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: "<player> <material|entity> <number>"
description: "adds offset for material or entity limit"
success: "&a Limit offset for [name] is increased till [number]."
remove:
parameters: "<player> <material|entity> <number>"
description: "reduces offset for material or entity limit"
success: "&a Limit offset for [name] is reduced till [number]."
reset:
parameters: "<player> <material|entity>"
description: "removes offset for material or entity"
success: "&a Limit offset for [name] is set to 0."
view:
parameters: "<player> <material|entity>"
description: "displays offset for material or entity"
message: "&a [name] offset is set to [number]."
island:
limits:
parameters: ""
limits:
description: "show your island limits"
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cNo limits set in this world"
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?"

View File

@ -0,0 +1,55 @@
---
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: "<jugador>"
description: mostrar los límites de la isla para el jugador
calc:
parameters: "<jugador>"
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: "<jugador> <material|entidad> <número>"
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: "<jugador> <material|entidad> <número>"
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: "<jugador> <material|entidad> <número>"
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: "<jugador> <material|entidad>"
description: elimina la compensación para material o entidad
success: "&a El desplazamiento límite para [number] está establecido en 0."
view:
parameters: "<jugador> <material|entidad>"
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: "&cNo hay limites establecidos en este mundo"
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?"

View File

@ -0,0 +1,30 @@
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: "<player>"
description: "affiche les limites de l'île pour le joueur"
calc:
parameters: "<player>"
description: "recalcule les limites de l'île pour le joueur"
finished: "&a Recomptage terminé avec succès!"
island:
limits:
description: "affichez les limites de votre île"
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cas de limites définies dans ce monde"
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?"

View File

@ -0,0 +1,33 @@
###########################################################################################
# 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] 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: "<player>"
description: "Egy játékos sziget limitjének megtekintése"
calc:
parameters: "<player>"
description: "Egy játékos sziget limitjének újraszámolása"
finished: "&aA Sziget újraszámolás sikeresen befejeződött!"
island:
limits:
description: "Sziget limitek megtekintése"
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cNincsenek limitek ebben a világban"
recount:
description: "Limitek újraszámolása a szigeteden"

View File

@ -0,0 +1,23 @@
---
block-limits:
hit-limit: "c[material]は[number]に制限されています!"
entity-limits:
hit-limit: "c[entity]の生成は[number]に制限されています!"
admin:
limits:
main:
parameters: "<プレイヤー>"
description: プレイヤーの島の制限を表示する
calc:
parameters: "<プレイヤー>"
finished: "a島の再計算が正常に完了しました"
description: プレイヤーの島の制限を再計算します
island:
limits:
block-limit-syntax: "[number]/[limit]"
description: 島の限界を示す
max-color: "c"
regular-color: "a"
no-limits: "cこの世界には制限がありません"
limits:
panel-title: 島の制限

View File

@ -0,0 +1,25 @@
---
admin:
limits:
calc:
description: pārrēķinā salas ierobežojumus priekš spēlētāja
parameters: "<spēlētājs>"
finished: "&aSalas ierobežojumu pārrēķināšana pabeigta!"
main:
description: rāda salas ierobežojumus priekš spēlētāja
parameters: "<spēlētājs>"
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]!"
island:
limits:
block-limit-syntax: "[number]/[limit]"
description: rāda tavas salas ierobežojumus
max-color: "&c"
no-limits: "&cŠai pasaulei nav uzstādīti ierobežojumi"
recount:
description: pārrēķina ierobežojumus tavai salai
regular-color: "&a"
limits:
panel-title: Salas ierobežojumi

View File

@ -0,0 +1,54 @@
---
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: "<gracz>"
description: pokazuje limity wysp gracza
calc:
parameters: "<gracz>"
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: "<gracz> <materiał|istota> <liczba>"
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: "<gracz> <materiał|istota> <liczba>"
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: "<gracz> <materiał|istota> <liczba>"
description: zmniejsza offset dla limitu materiału lub podmiotu
success: "&a Limit dla bloku [name] \nzostał zmniejszony do [number]."
reset:
parameters: "<gracz> <materiał|istota>"
description: usuwa wartość dla materiału lub istoty
success: "&a Limit dla [name] jest ustawione na 0."
view:
parameters: "<gracz> <materiał|istota>"
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: "&cBrak ustawionych limitów."
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?"

View File

@ -0,0 +1,25 @@
---
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: "<jucator>"
description: Arata limitele insulei pentru jucator
calc:
parameters: "<jucator>"
description: recalculeaza limitele insulei pentru jocator
finished: "&aRecalcularea insulei terminata cu succes!"
island:
limits:
description: iti arata limitele insulei
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&cNici o limita setata in aceasta lume"
recount:
description: renumara limitele insulei tale

View File

@ -0,0 +1,25 @@
---
admin:
limits:
calc:
description: Oyuncu için ada limitlerini tekrar hesapla.
parameters: "<player>"
finished: "&aAda hesaplaması başarıyla yapıldı."
main:
description: Oyuncu için ada limitlerini göster.
parameters: "<player>"
island:
limits:
block-limit-syntax: "&5[number]&7/&e[limit]"
description: Ada limitlerini gör.
max-color: "&c"
regular-color: "&9"
no-limits: "&4Bu dünyada limitler ayarlı değil!"
recount:
description: Ada limitlerini tekrardan hesaplar.
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"

View File

@ -0,0 +1,32 @@
###########################################################################################
# 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] 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: "<người chơi>"
description: "xem giới hạn đảo của người chơi"
calc:
parameters: "<người chơi>"
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!"
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: "&cKhông có giới hạn ở thế giới này"
recount:
description: "tính toán lại giới hạn đảo của bạn"

View File

@ -0,0 +1,35 @@
###########################################################################################
# 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] 已限制到 [number]!"
entity-limits:
hit-limit: "&c[entity] 生成已限制到 [number]!"
limits:
panel-title: "岛屿限制"
admin:
limits:
main:
parameters: "<player>"
description: "显示玩家的限制"
calc:
parameters: "<player>"
description: "重新计算玩家的岛屿限制"
finished: "&a岛屿重计算已完成!"
island:
limits:
description: "显示您的岛屿限制"
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c此世界中无限制"
recount:
description: "重新计数岛屿限制"
now-recounting: "&b 开始重新计算. 可能需要一定的时间, 请稍等......"
in-progress: "&c 重新计算岛屿限制中. 请稍等......"
time-out: "&c 重新计算超时. 岛屿太大了吗?"

View File

@ -0,0 +1,9 @@
name: BentoBox-Limits
main: world.bentobox.limits.LimitsPladdon
version: ${project.version}${build.number}
api-version: "1.19"
authors: [tastybento]
contributors: ["The BentoBoxWorld Community"]
website: https://bentobox.world
description: ${project.description}

View File

@ -0,0 +1,541 @@
package bentobox.addon.limits.listeners;
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.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.Limits;
import world.bentobox.limits.Settings;
import world.bentobox.limits.listeners.BlockLimitsListener;
import world.bentobox.limits.listeners.JoinListener;
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;
@Before
public void setUp() {
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 Settings.EntityGroup("friendly", new HashSet<>(), -1))));
// Island Manager
when(island.getUniqueId()).thenReturn("unique_id");
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.randomUUID());
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);
// Island
when(island.getOwner()).thenReturn(UUID.randomUUID());
}
/**
* 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<PermissionAttachmentInfo> 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<Material, Integer> 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<Material, Integer> 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();
}
}

View File

@ -0,0 +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));
}
}

View File

@ -0,0 +1,148 @@
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.bukkit.util.BoundingBox;
import org.junit.After;
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<Entity> 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());
}
}