Compare commits

...

248 Commits

Author SHA1 Message Date
tastybento
52c12e2a7d Fix tests and update API requirements 2024-11-10 13:54:22 -08:00
tastybento
deda533db1
Merge pull request #229 from BentoBoxWorld/MC_1_21_3
MC 1.21.3 version
2024-11-09 12:31:05 -08:00
tastybento
1d73ce8a23
Update pom.xml 2024-11-08 21:44:32 -08:00
tastybento
d1030ca172 MC 1.21.3 version 2024-11-08 21:34:46 -08:00
tastybento
59ea22ce47 Fix locale 2024-10-07 13:50:49 -07:00
tastybento
a72acac16f Version 1.25.1 2024-10-07 13:49:19 -07:00
tastybento
2d6d768b14
Update ja.yml 2024-10-05 11:30:49 -07:00
tastybento
1bca34560f
Merge pull request #224 from BentoBoxWorld/gitlocalize-31167
Taiwanese
2024-10-05 11:08:59 -07:00
tastybento
ae13fb2cf5
Merge pull request #223 from BentoBoxWorld/gitlocalize-31166
Vietnamese translation
2024-10-05 11:08:47 -07:00
tastybento
c880bb300e
Merge pull request #225 from BentoBoxWorld/gitlocalize-31168
Portugal
2024-10-05 11:08:36 -07:00
tastybento
3c2500c125
Merge pull request #222 from BentoBoxWorld/gitlocalize-31165
Ukranian
2024-10-05 11:08:26 -07:00
tastybento
abcfa539fc
Merge pull request #216 from BentoBoxWorld/gitlocalize-31159
Polish
2024-10-05 11:08:15 -07:00
tastybento
6c8de4424a
Merge pull request #215 from BentoBoxWorld/gitlocalize-31158
Polish
2024-10-05 11:08:03 -07:00
tastybento
463f28478f
Merge pull request #213 from BentoBoxWorld/gitlocalize-31156
Japanese translationnse
2024-10-05 11:07:52 -07:00
tastybento
28576ed2fd
Merge pull request #211 from BentoBoxWorld/gitlocalize-31154
Indonesian
2024-10-05 11:07:39 -07:00
tastybento
5199505f96
Merge pull request #210 from BentoBoxWorld/gitlocalize-31153
Croatian translationan
2024-10-05 11:07:26 -07:00
tastybento
9651c5dac9
Merge pull request #209 from BentoBoxWorld/gitlocalize-31152
French translationrench
2024-10-05 11:07:16 -07:00
tastybento
74a9223a52
Merge pull request #208 from BentoBoxWorld/gitlocalize-31151
German
2024-10-05 11:07:05 -07:00
tastybento
ab1e5fe23b
Merge pull request #207 from BentoBoxWorld/gitlocalize-31150
Czech
2024-10-05 11:06:56 -07:00
tastybento
7144683661
Merge pull request #214 from BentoBoxWorld/gitlocalize-31157
Chinese
2024-10-05 11:06:41 -07:00
tastybento
ecc017318d
Merge pull request #217 from BentoBoxWorld/gitlocalize-31160
Romanian
2024-10-05 11:06:28 -07:00
tastybento
ddad296227
Merge pull request #218 from BentoBoxWorld/gitlocalize-31161
Korean
2024-10-05 11:06:19 -07:00
tastybento
5d0420e0b5
Merge pull request #221 from BentoBoxWorld/gitlocalize-31164
Turkish
2024-10-05 11:06:10 -07:00
tastybento
fcea94d09c
Merge pull request #219 from BentoBoxWorld/gitlocalize-31162
Latvian
2024-10-05 11:06:01 -07:00
tastybento
f805c98b26
Merge pull request #212 from BentoBoxWorld/gitlocalize-31155
Italian
2024-10-05 11:05:51 -07:00
tastybento
69c5a852ea
Merge pull request #220 from BentoBoxWorld/gitlocalize-31163
Dutch
2024-10-05 11:05:39 -07:00
tastybento
ecb5b3bfe3
Merge pull request #206 from BentoBoxWorld/gitlocalize-31149
Spanish
2024-10-05 11:05:25 -07:00
mt-gitlocalize
c0eae7f986 Translate pt.yml via GitLocalize 2024-10-05 18:04:32 +00:00
tastybento
412553b408 Translate pt.yml via GitLocalize 2024-10-05 18:04:31 +00:00
mt-gitlocalize
266823f9e7 Translate zh-TW.yml via GitLocalize 2024-10-05 18:02:29 +00:00
tastybento
8e601a1357 Translate zh-TW.yml via GitLocalize 2024-10-05 18:02:28 +00:00
tastybento
1430abafc0 Translate vi.yml via GitLocalize 2024-10-05 18:00:06 +00:00
mt-gitlocalize
fc4c652d5b Translate vi.yml via GitLocalize 2024-10-05 18:00:04 +00:00
mt-gitlocalize
a63d980580 Translate uk.yml via GitLocalize 2024-10-05 17:57:57 +00:00
tastybento
5d908dff37 Translate uk.yml via GitLocalize 2024-10-05 17:57:55 +00:00
tastybento
c03583de5f Translate tr.yml via GitLocalize 2024-10-05 17:55:30 +00:00
mt-gitlocalize
c7be909aed Translate tr.yml via GitLocalize 2024-10-05 17:55:29 +00:00
mt-gitlocalize
701e758a75 Translate nl.yml via GitLocalize 2024-10-05 17:53:31 +00:00
tastybento
c1c656739f Translate nl.yml via GitLocalize 2024-10-05 17:53:30 +00:00
tastybento
5b74acb323 Translate lv.yml via GitLocalize 2024-10-05 17:50:30 +00:00
mt-gitlocalize
68cc0b8436 Translate lv.yml via GitLocalize 2024-10-05 17:50:30 +00:00
mt-gitlocalize
cf9159b308 Translate ko.yml via GitLocalize 2024-10-05 17:48:26 +00:00
tastybento
09f25d4811 Translate ko.yml via GitLocalize 2024-10-05 17:48:25 +00:00
tastybento
574af3294e Translate ro.yml via GitLocalize 2024-10-05 17:45:24 +00:00
mt-gitlocalize
ac42be3e06 Translate ro.yml via GitLocalize 2024-10-05 17:45:24 +00:00
tastybento
70d7c0e180 Translate hu.yml via GitLocalize 2024-10-05 17:43:35 +00:00
mt-gitlocalize
ba442a789f Translate hu.yml via GitLocalize 2024-10-05 17:43:34 +00:00
tastybento
88c8343036 Translate pl.yml via GitLocalize 2024-10-05 17:40:45 +00:00
mt-gitlocalize
66cab9e74d Translate pl.yml via GitLocalize 2024-10-05 17:40:44 +00:00
mt-gitlocalize
53167b4fa0 Translate zh-CN.yml via GitLocalize 2024-10-05 17:39:22 +00:00
tastybento
9d1933ed4c Translate zh-CN.yml via GitLocalize 2024-10-05 17:39:21 +00:00
tastybento
dd436834b1 Translate ja.yml via GitLocalize 2024-10-05 17:37:15 +00:00
mt-gitlocalize
e9b66c16f1 Translate ja.yml via GitLocalize 2024-10-05 17:37:14 +00:00
mt-gitlocalize
2daff776ff Translate it.yml via GitLocalize 2024-10-05 17:35:04 +00:00
tastybento
3123e8d146 Translate it.yml via GitLocalize 2024-10-05 17:35:03 +00:00
Excel Dwi Oktavianto
31ea783858 Translate id.yml via GitLocalize 2024-10-05 17:32:52 +00:00
mt-gitlocalize
12b1e264d3 Translate id.yml via GitLocalize 2024-10-05 17:32:51 +00:00
Tiara Renata
35988f680a Translate id.yml via GitLocalize 2024-10-05 17:32:49 +00:00
tastybento
79573eae88 Translate id.yml via GitLocalize 2024-10-05 17:32:48 +00:00
mt-gitlocalize
e43d85d487 Translate hr.yml via GitLocalize 2024-10-05 17:30:32 +00:00
tastybento
b02d3e42c4 Translate hr.yml via GitLocalize 2024-10-05 17:30:31 +00:00
tastybento
22d25c0181 Translate fr.yml via GitLocalize 2024-10-05 17:28:16 +00:00
mt-gitlocalize
ec7f0e4314 Translate fr.yml via GitLocalize 2024-10-05 17:28:16 +00:00
Patrick
30c25ee3bc Translate de.yml via GitLocalize 2024-10-05 17:25:01 +00:00
tastybento
11f3aa500c Translate de.yml via GitLocalize 2024-10-05 17:25:00 +00:00
mt-gitlocalize
95c3e82fdb Translate de.yml via GitLocalize 2024-10-05 17:24:59 +00:00
tastybento
e472fddec2 Translate cs.yml via GitLocalize 2024-10-05 17:23:01 +00:00
mt-gitlocalize
a167bc220b Translate cs.yml via GitLocalize 2024-10-05 17:23:01 +00:00
tastybento
fba0981d61 Translate es.yml via GitLocalize 2024-10-05 17:21:02 +00:00
mt-gitlocalize
592f0413b3 Translate es.yml via GitLocalize 2024-10-05 17:21:01 +00:00
tastybento
78c07f0101
Merge pull request #205 from BentoBoxWorld/Better_GUI_titles
Makes GUI titles configurable in the locale files
2024-10-05 10:19:03 -07:00
tastybento
37e4c10964 Makes GUI titles configurable in the locale files
Idea from Discord
2024-10-05 10:17:43 -07:00
tastybento
2bb48e0139
Merge pull request #203 from BentoBoxWorld/owner_perms_only
Owner perms only
2024-10-05 01:17:19 -07:00
tastybento
9f4c9315d6 Version 1.25.0 2024-10-05 01:16:00 -07:00
tastybento
79bf0f00c3 Fix test 2024-10-05 01:15:27 -07:00
tastybento
5f0102ee10 Merge branch 'develop' of https://github.com/BentoBoxWorld/Limits.git into develop 2024-10-05 01:10:14 -07:00
tastybento
e317f27869 Make island owner the setter of limits 2024-10-05 01:10:08 -07:00
tastybento
923ce3ad17 Make island owner the setting of limtis 2024-10-05 01:09:55 -07:00
tastybento
eb5ceba4c7 Version 1.24.0 2024-09-22 15:17:30 -07:00
tastybento
902b597b11
Merge pull request #200 from BentoBoxWorld/195_animal_monster_limit
Add default Animal and Monster entity groups to config.yml #195
2024-09-20 21:29:46 -07:00
tastybento
50347bb922 Add default Animal and Monster entity groups to config.yml #195
These were already supported but I also added an icon option.
2024-09-20 19:48:43 -07:00
tastybento
f08c45822d Add NPE protection 2024-09-19 21:54:05 -07:00
tastybento
6b9262efc7 Check user perms not just on login for limits 2024-09-19 21:50:40 -07:00
tastybento
e2670a49e0 Version 1.23.0 2024-09-19 21:50:18 -07:00
tastybento
40fdf283b1 Fix for snowmen 2024-07-17 16:47:34 -07:00
tastybento
6dedd50db5 Fix for Jack O'lantern iron exploit #190 2024-07-17 16:42:27 -07:00
tastybento
19373fa9b3 Added Limits test class 2024-07-17 13:59:45 -07:00
tastybento
674614eb22 Fix tests. Build against 1.20.5 to avoid test issues 2024-07-16 21:57:27 -07:00
tastybento
c0d965daa6 Update to 1.21 2024-07-16 21:52:52 -07:00
tastybento
2ea2c4526e Shift to Minecraft 1.21 2024-07-16 21:51:06 -07:00
tastybento
0db1cfc2b8 Version 1.22.0 2024-07-16 21:50:53 -07:00
tastybento
397e6d281e Version 1.21.1 2024-07-05 17:07:51 -07:00
tastybento
89af2ae996 Return the pladdon that was made. 2024-07-02 09:33:48 -07:00
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
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
58 changed files with 5390 additions and 947 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

View File

@ -1,22 +0,0 @@
language: java
sudo: false
addons:
sonarcloud:
organization: "bentobox-world"
jdk:
- openjdk8
- openjdk11
matrix:
allow_failures:
- jdk: openjdk11
script:
# the following command line builds the project, runs the tests with coverage and then execute the SonarCloud analysis
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=BentoBoxWorld_Limits
cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.sonar/cache'

119
pom.xml
View File

@ -39,13 +39,9 @@
</issueManagement> </issueManagement>
<distributionManagement> <distributionManagement>
<snapshotRepository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.org/repository/maven-snapshots</url>
</snapshotRepository>
<repository> <repository>
<id>codemc-releases</id> <id>bentoboxworld</id>
<url>https://repo.codemc.org/repository/maven-releases</url> <url>https://repo.codemc.org/repository/bentoboxworld/</url>
</repository> </repository>
</distributionManagement> </distributionManagement>
@ -53,18 +49,21 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>17</java.version>
<!-- Non-minecraft related dependencies --> <!-- Non-minecraft related dependencies -->
<powermock.version>2.0.2</powermock.version> <powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions --> <!-- More visible way how to change dependency versions -->
<spigot.version>1.15.2-R0.1-SNAPSHOT</spigot.version> <spigot.version>1.21.3-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.12.0</bentobox.version> <bentobox.version>2.7.1-SNAPSHOT</bentobox.version>
<!-- Revision variable removes warning about dynamic version --> <!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision> <revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. --> <!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number> <build.number>-LOCAL</build.number>
<!-- This allows to change between versions. --> <!-- This allows to change between versions. -->
<build.version>1.12.1</build.version> <build.version>1.26.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> </properties>
<!-- Profiles will allow to automatically change build version. --> <!-- Profiles will allow to automatically change build version. -->
@ -107,30 +106,6 @@
<build.number></build.number> <build.number></build.number>
</properties> </properties>
</profile> </profile>
<profile>
<id>sonar</id>
<properties>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>bentobox-world</sonar.organization>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.6.0.1398</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sonar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<repositories> <repositories>
@ -139,12 +114,12 @@
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
</repository> </repository>
<repository> <repository>
<id>codemc</id> <id>bentoboxworld</id>
<url>https://repo.codemc.org/repository/maven-snapshots/</url> <url>https://repo.codemc.org/repository/bentoboxworld/</url>
</repository> </repository>
<repository> <repository>
<id>codemc-repo</id> <id>codemc</id>
<url>https://repo.codemc.org/repository/maven-public/</url> <url>https://repo.codemc.org/repository/maven-snapshots/</url>
</repository> </repository>
</repositories> </repositories>
@ -160,7 +135,7 @@
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>3.0.0</version> <version>3.11.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -217,19 +192,42 @@
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0</version>
</plugin> </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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version> <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>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -249,30 +247,45 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.3</version> <version>0.8.10</version>
<configuration> <configuration>
<append>true</append> <append>true</append>
<excludes> <excludes>
<!-- This is required to prevent Jacoco from adding <!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) --> synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude> <exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes> </excludes>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>pre-unit-test</id> <id>prepare-agent</id>
<goals> <goals>
<goal>prepare-agent</goal> <goal>prepare-agent</goal>
</goals> </goals>
</execution> </execution>
<execution> <execution>
<id>post-unit-test</id> <id>report</id>
<goals> <goals>
<goal>report</goal> <goal>report</goal>
</goals> </goals>
<configuration>
<formats>
<format>XML</format>
</formats>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </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> </plugins>
</build> </build>

View File

@ -0,0 +1,72 @@
package world.bentobox.limits;
import java.util.Objects;
import java.util.Set;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
/**
* A named class representing a group of entities and their limits
*
*/
public class EntityGroup {
private final String name;
private final Set<EntityType> types;
private final int limit;
private final Material icon;
public EntityGroup(String name, Set<EntityType> types, int limit, Material icon) {
this.name = name;
this.types = types;
this.limit = limit;
this.icon = icon;
}
public boolean contains(EntityType type) {
return types.contains(type);
}
public String getName() {
return name;
}
public Set<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);
}
/**
* @return the icon
*/
public Material getIcon() {
return Objects.requireNonNullElse(icon, Material.BARRIER);
}
@Override
public String toString() {
return "EntityGroup [name=" + name + ", types=" + types + ", limit=" + limit + ", icon=" + icon + "]";
}
}

View File

@ -1,17 +1,26 @@
package world.bentobox.limits; package world.bentobox.limits;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.World; 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.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.limits.commands.AdminCommand; import world.bentobox.bentobox.api.user.User;
import world.bentobox.limits.commands.PlayerCommand; 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.BlockLimitsListener;
import world.bentobox.limits.listeners.EntityLimitListener; import world.bentobox.limits.listeners.EntityLimitListener;
import world.bentobox.limits.listeners.JoinListener; import world.bentobox.limits.listeners.JoinListener;
import world.bentobox.limits.objects.IslandBlockCount;
/** /**
@ -21,9 +30,11 @@ import world.bentobox.limits.listeners.JoinListener;
*/ */
public class Limits extends Addon { public class Limits extends Addon {
private static final String LIMIT_NOT_SET = "Limit not set";
private Settings settings; private Settings settings;
private List<GameModeAddon> gameModes; private List<GameModeAddon> gameModes = new ArrayList<>();
private BlockLimitsListener blockLimitListener; private BlockLimitsListener blockLimitListener;
private JoinListener joinListener;
@Override @Override
public void onDisable(){ public void onDisable(){
@ -47,13 +58,15 @@ public class Limits extends Addon {
// Register commands // Register commands
gm.getAdminCommand().ifPresent(a -> new AdminCommand(this, a)); gm.getAdminCommand().ifPresent(a -> new AdminCommand(this, a));
gm.getPlayerCommand().ifPresent(a -> new PlayerCommand(this, a)); gm.getPlayerCommand().ifPresent(a -> new PlayerCommand(this, a));
registerPlaceholders(gm);
log("Limits will apply to " + gm.getDescription().getName()); log("Limits will apply to " + gm.getDescription().getName());
} }
); );
// Register listener // Register listener
blockLimitListener = new BlockLimitsListener(this); blockLimitListener = new BlockLimitsListener(this);
registerListener(blockLimitListener); registerListener(blockLimitListener);
registerListener(new JoinListener(this)); joinListener = new JoinListener(this);
registerListener(joinListener);
registerListener(new EntityLimitListener(this)); registerListener(new EntityLimitListener(this));
// Done // Done
} }
@ -98,9 +111,9 @@ public class Limits extends Addon {
} }
/** /**
* Get the name of the game mode for this world * Get the permission prefix for this world
* @param world - world * @param world - world
* @return game mode name or empty string if none * @return permisdsion prefix or empty string if none
*/ */
public String getGameModePermPrefix(World world) { public String getGameModePermPrefix(World world) {
return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(GameModeAddon::getPermissionPrefix).orElse(""); return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(GameModeAddon::getPermissionPrefix).orElse("");
@ -116,4 +129,139 @@ public class Limits extends Addon {
return gameModes.stream().anyMatch(gm -> gm.getDescription().getName().equals(gameMode)); return gameModes.stream().anyMatch(gm -> gm.getDescription().getName().equals(gameMode));
} }
/**
* @return the joinListener
*/
public JoinListener getJoinListener() {
return joinListener;
}
private void registerPlaceholders(GameModeAddon gm) {
if (getPlugin().getPlaceholdersManager() == null) return;
Registry.MATERIAL.stream()
.filter(Material::isBlock)
.forEach(m -> registerCountAndLimitPlaceholders(m, gm));
Arrays.stream(EntityType.values())
.forEach(e -> registerCountAndLimitPlaceholders(e, gm));
}
/**
* Registers placeholders for the count and limit of the material
* in the format of %Limits_(gamemode prefix)_island_(lowercase material name)_count%
* and %Limits_(gamemode prefix)_island_(lowercase material name)_limit%
*
* Example: registerCountAndLimitPlaceholders("HOPPER", gm);
* Placeholders:
* "Limits_bskyblock_island_hopper_count"
* "Limits_bskyblock_island_hopper_limit"
* "Limits_bskyblock_island_hopper_base_limit"
* "Limits_bskyblock_island_zombie_limit"
*
* @param m material
* @param gm game mode
*/
private void registerCountAndLimitPlaceholders(Material m, GameModeAddon gm) {
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_count",
user -> String.valueOf(getCount(user, m, gm)));
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_limit",
user -> getLimit(user, m, gm));
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_base_limit",
user -> getBaseLimit(user, m, gm));
}
private void registerCountAndLimitPlaceholders(EntityType e, GameModeAddon gm) {
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_limit",
user -> getLimit(user, e, gm));
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_base_limit",
user -> getBaseLimit(user, e, gm));
}
/**
* @param user - Used to identify the island the user belongs to
* @param m - The material we are trying to count on the island
* @param gm Game Mode Addon
* @return Number of blocks of the specified material on the given user's island
*/
private int getCount(@Nullable User user, Material m, GameModeAddon gm) {
Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
if (is == null) {
return 0;
}
@Nullable IslandBlockCount ibc = getBlockLimitListener().getIsland(is.getUniqueId());
if (ibc == null) {
return 0;
}
return ibc.getBlockCount(m);
}
/**
* @param user - Used to identify the island the user belongs to
* @param m - The material whose limit we are querying
* @param gm Game Mode Addon
* @return The limit of the specified material on the given user's island
*/
private String getLimit(@Nullable User user, Material m, GameModeAddon gm) {
Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
if (is == null) {
return LIMIT_NOT_SET;
}
if (user != null) {
// Check the permissions of the user and update
this.getJoinListener().checkPerms(user.getPlayer(), gm.getPermissionPrefix() + "island.limit.",
is.getUniqueId(), gm.getDescription().getName());
}
int limit = this.getBlockLimitListener().
getMaterialLimits(is.getWorld(), is.getUniqueId()).getOrDefault(m, -1);
return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
}
private String getBaseLimit(@Nullable User user, Material m, GameModeAddon gm) {
Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
if (is == null) {
return LIMIT_NOT_SET;
}
int limit = this.getBlockLimitListener().
getMaterialLimits(is.getWorld(), is.getUniqueId()).
getOrDefault(m, -1);
if (limit > 0) {
limit -= this.getBlockLimitListener().getIsland(is).getBlockLimitOffset(m);
}
return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
}
private String getLimit(@Nullable User user, EntityType e, GameModeAddon gm) {
Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
if (is == null) {
return LIMIT_NOT_SET;
}
int limit = this.getBlockLimitListener().getIsland(is).getEntityLimit(e);
if (limit < 0 && this.getSettings().getLimits().containsKey(e)) {
limit = this.getSettings().getLimits().get(e);
}
return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
}
private String getBaseLimit(@Nullable User user, EntityType e, GameModeAddon gm) {
Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
if (is == null || !this.getSettings().getLimits().containsKey(e)) {
return LIMIT_NOT_SET;
}
int limit = this.getSettings().getLimits().get(e);
return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
}
} }

View File

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

View File

@ -1,28 +1,43 @@
package world.bentobox.limits; package world.bentobox.limits;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
public class Settings { public class Settings {
enum GeneralGroup {
ANIMALS, MOBS
}
private final Map<GeneralGroup, Integer> general = new EnumMap<>(GeneralGroup.class);
private final Map<EntityType, Integer> limits = new EnumMap<>(EntityType.class); 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 List<String> gameModes;
private final boolean asyncGolums;
private static final List<EntityType> DISALLOWED = Arrays.asList( private static final List<EntityType> DISALLOWED = Arrays.asList(
EntityType.PRIMED_TNT, EntityType.TNT,
EntityType.EVOKER_FANGS, EntityType.EVOKER_FANGS,
EntityType.LLAMA_SPIT, EntityType.LLAMA_SPIT,
EntityType.DRAGON_FIREBALL, EntityType.DRAGON_FIREBALL,
EntityType.AREA_EFFECT_CLOUD, EntityType.AREA_EFFECT_CLOUD,
EntityType.ENDER_SIGNAL, EntityType.END_CRYSTAL,
EntityType.SMALL_FIREBALL, EntityType.SMALL_FIREBALL,
EntityType.FIREBALL, EntityType.FIREBALL,
EntityType.THROWN_EXP_BOTTLE, EntityType.EXPERIENCE_BOTTLE,
EntityType.EXPERIENCE_ORB, EntityType.EXPERIENCE_ORB,
EntityType.SHULKER_BULLET, EntityType.SHULKER_BULLET,
EntityType.WITHER_SKULL, EntityType.WITHER_SKULL,
@ -31,9 +46,8 @@ public class Settings {
EntityType.SPECTRAL_ARROW, EntityType.SPECTRAL_ARROW,
EntityType.SNOWBALL, EntityType.SNOWBALL,
EntityType.EGG, EntityType.EGG,
EntityType.LEASH_HITCH, EntityType.LEASH_KNOT,
EntityType.GIANT, EntityType.GIANT,
EntityType.ENDER_CRYSTAL,
EntityType.ENDER_PEARL, EntityType.ENDER_PEARL,
EntityType.ENDER_DRAGON, EntityType.ENDER_DRAGON,
EntityType.ITEM_FRAME, EntityType.ITEM_FRAME,
@ -47,6 +61,11 @@ public class Settings {
ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits"); ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits");
if (el != null) { if (el != null) {
for (String key : el.getKeys(false)) { for (String key : el.getKeys(false)) {
if (key.equalsIgnoreCase("ANIMALS")) {
general.put(GeneralGroup.ANIMALS, el.getInt(key, 0));
} else if (key.equalsIgnoreCase("MOBS")) {
general.put(GeneralGroup.MOBS, el.getInt(key, 0));
} else {
EntityType type = getType(key); EntityType type = getType(key);
if (type != null) { if (type != null) {
if (DISALLOWED.contains(type)) { if (DISALLOWED.contains(type)) {
@ -59,8 +78,52 @@ public class Settings {
} }
} }
} }
}
// Async Golums
asyncGolums = addon.getConfig().getBoolean("async-golums", true);
addon.log("Entity limits:"); addon.log("Entity limits:");
limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log); limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log);
//group limits
el = addon.getConfig().getConfigurationSection("entitygrouplimits");
if (el != null) {
for (String name : el.getKeys(false)) {
int limit = el.getInt(name + ".limit");
String iconName = el.getString(name + ".icon", "BARRIER");
Material icon = Material.BARRIER;
try {
icon = Material.valueOf(iconName.toUpperCase(Locale.ENGLISH));
} catch (Exception e) {
addon.logError("Invalid group icon name: " + iconName + ". Use a Bukkit Material.");
icon = Material.BARRIER;
}
Set<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, icon);
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) { private EntityType getType(String key) {
@ -68,12 +131,26 @@ public class Settings {
} }
/** /**
* @return the limits * @return the entity limits
*/ */
public Map<EntityType, Integer> getLimits() { public Map<EntityType, Integer> getLimits() {
return Collections.unmodifiableMap(limits); 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 * @return the gameModes
*/ */
@ -81,4 +158,17 @@ public class Settings {
return gameModes; return gameModes;
} }
/**
* @return the asyncGolums
*/
public boolean isAsyncGolums() {
return asyncGolums;
}
/**
* @return the general coverage map
*/
public Map<GeneralGroup, Integer> getGeneral() {
return general;
}
} }

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,158 +0,0 @@
package world.bentobox.limits.commands;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import com.google.common.collect.ImmutableMap;
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;
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;
// 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.PIG_ZOMBIE, Material.ZOMBIE_PIGMAN_SPAWN_EGG)
.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();
}
/**
* @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;
}
// 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.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());
}
// Entity limits
Map<EntityType, Integer> map = new HashMap<>(addon.getSettings().getLimits());
// Merge in any permission-based limits
if (ibc != null) ibc.getEntityLimits().forEach(map::put);
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.toString() + "_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)));
pb.item(pib.build());
});
pb.build();
}
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;
}
}

View File

@ -1,125 +0,0 @@
package world.bentobox.limits.commands;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.World;
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;
import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.Limits;
import world.bentobox.limits.listeners.BlockLimitsListener;
import world.bentobox.limits.objects.IslandBlockCount;
/**
*
* @author YellowZaki, tastybento
*/
public class LimitsCalc {
private final Limits addon;
private final World world;
private final Island island;
private final BlockLimitsListener bll;
private IslandBlockCount ibc;
private final Map<Material, AtomicInteger> blockCount;
private final User sender;
private final Set<Pair<Integer, Integer>> chunksToScan;
private int count;
LimitsCalc(World world, BentoBox instance, UUID targetPlayer, Limits addon, User sender) {
this.addon = addon;
this.island = instance.getIslands().getIsland(world, targetPlayer);
this.bll = addon.getBlockLimitListener();
this.ibc = bll.getIsland(island.getUniqueId());
blockCount = new EnumMap<>(Material.class);
this.sender = sender;
this.world = world;
// Get chunks to scan
chunksToScan = getChunksToScan(island);
count = 0;
chunksToScan.forEach(c -> Util.getChunkAtAsync(world, c.x, c.z).thenAccept(ch -> {
ChunkSnapshot snapShot = ch.getChunkSnapshot();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
this.scanChunk(snapShot);
count++;
if (count == chunksToScan.size()) {
this.tidyUp();
}
});
}));
}
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 < Objects.requireNonNull(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, new AtomicInteger(1));
} else {
blockCount.get(md).getAndIncrement();
}
}
}
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() {
if (ibc == null) {
ibc = new IslandBlockCount();
}
ibc.setBlockCount(blockCount.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get())));
bll.setIsland(island.getUniqueId(), ibc);
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished"));
}
}

View File

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

View File

@ -1,4 +1,4 @@
package world.bentobox.limits.commands; package world.bentobox.limits.commands.admin;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -7,16 +7,19 @@ import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.calculators.Pipeliner;
/** /**
* *
* @author YellowZaki * @author YellowZaki, tastybento
*/ */
public class CalcCommand extends CompositeCommand { public class CalcCommand extends CompositeCommand {
private final Limits addon; private final Limits addon;
private Island island;
/** /**
* Admin command * Admin command
@ -49,10 +52,26 @@ public class CalcCommand extends CompositeCommand {
if (playerUUID == null) { if (playerUUID == null) {
user.sendMessage("general.errors.unknown-player", args.get(0)); user.sendMessage("general.errors.unknown-player", args.get(0));
return true; return true;
}
island = addon.getIslands().getIsland(getWorld(), playerUUID);
if (island == null) {
user.sendMessage("general.errors.player-has-no-island");
return false;
} else { } else {
//Calculate //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; return true;
} else { } else {
showHelp(this, user); showHelp(this, user);
@ -60,13 +79,7 @@ public class CalcCommand extends CompositeCommand {
} }
} }
private 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 @Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) { public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {

View File

@ -0,0 +1,695 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.limits.commands.admin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.Limits;
import world.bentobox.limits.objects.IslandBlockCount;
/**
* This command manages offsets to the player island limits.
*/
public class OffsetCommand extends CompositeCommand
{
/**
* Instantiates a new Offset command.
*
* @param addon the addon
* @param parent the parent
*/
public OffsetCommand(Limits addon, CompositeCommand parent)
{
super(parent, "offset");
new OffsetSetCommand(addon, this);
new OffsetAddCommand(addon, this);
new OffsetRemoveCommand(addon, this);
new OffsetResetCommand(addon, this);
new OffsetDisplayCommand(addon, this);
}
@Override
public void setup()
{
this.setOnlyPlayer(false);
this.setPermission("admin.limits.offset");
this.setParametersHelp("admin.limits.offset.parameters");
this.setDescription("admin.limits.offset.description");
}
@Override
public boolean execute(User user, String s, List<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.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.Tab;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.EntityGroup;
import world.bentobox.limits.Limits;
import world.bentobox.limits.objects.IslandBlockCount;
/**
* @author tastybento
*
*/
public class LimitTab implements Tab {
enum SORT_BY {
A2Z,
Z2A
}
/**
* This maps the entity types to the icon that should be shown in the panel
* If the icon is null, then the entity type is not covered by the addon
*/
private static final Map<EntityType, Material> E2M = ImmutableMap.<EntityType, Material>builder()
.put(EntityType.MOOSHROOM, Material.MOOSHROOM_SPAWN_EGG).put(EntityType.SNOW_GOLEM, Material.SNOW_BLOCK)
.put(EntityType.IRON_GOLEM, Material.IRON_BLOCK)
.put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG)
.put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL)
//.put(EntityType.BOAT, Material.OAK_BOAT)
.put(EntityType.ARMOR_STAND, Material.ARMOR_STAND)
.put(EntityType.ITEM_FRAME, Material.ITEM_FRAME)
.put(EntityType.PAINTING, Material.PAINTING)
// Minecarts
.put(EntityType.TNT_MINECART, Material.TNT_MINECART).put(EntityType.CHEST_MINECART, Material.CHEST_MINECART)
.put(EntityType.COMMAND_BLOCK_MINECART, Material.COMMAND_BLOCK_MINECART)
.put(EntityType.FURNACE_MINECART, Material.FURNACE_MINECART)
.put(EntityType.HOPPER_MINECART, Material.HOPPER_MINECART)
.put(EntityType.SPAWNER_MINECART, Material.MINECART)
//.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT)
.build();
// This is a map of blocks to Items
private static final Map<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).put(Material.MELON_STEM, Material.MELON)
.put(Material.PUMPKIN_STEM, Material.PUMPKIN);
// Block to Material icons
Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES"))));
Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO"))));
B2M = builder.build();
}
private final World world;
private final User user;
private final Limits addon;
private final List<@Nullable PanelItem> result;
private final SORT_BY sortBy;
public LimitTab(Limits addon, IslandBlockCount ibc, Map<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();
pib.name(user.getTranslation("island.limits.panel.entity-group-name-syntax", TextVariables.NAME,
v.getName()));
String description = "";
description += "(" + prettyNames(v) + ")\n";
pib.icon(v.getIcon());
long count = getCount(island, v);
String color = count >= limit ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
description += color
+ user.getTranslation("island.limits.block-limit-syntax",
TextVariables.NUMBER, String.valueOf(count),
"[limit]", String.valueOf(limit));
pib.description(description);
result.add(pib.build());
});
}
private void addEntityLimits(IslandBlockCount ibc, Island island) {
// Entity limits
Map<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(user.getTranslation("island.limits.panel.entity-name-syntax", TextVariables.NAME,
Util.prettifyText(k.toString())));
Material m;
try {
if (E2M.containsKey(k)) {
m = E2M.get(k);
} else if (k.isAlive()) {
m = Material.valueOf(k + "_SPAWN_EGG");
} else {
// Regular material
m = Material.valueOf(k.toString());
}
} catch (Exception e) {
m = Material.BARRIER;
}
pib.icon(m);
long count = getCount(island, k);
String color = count >= v ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
pib.description(color
+ user.getTranslation("island.limits.block-limit-syntax",
TextVariables.NUMBER, String.valueOf(count),
"[limit]", String.valueOf(v)));
result.add(pib.build());
});
}
private void addMaterialIcons(IslandBlockCount ibc, Map<Material, Integer> matLimits) {
// Material limits
for (Entry<Material, Integer> en : matLimits.entrySet()) {
PanelItemBuilder pib = new PanelItemBuilder();
pib.name(user.getTranslation("island.limits.panel.block-name-syntax", TextVariables.NAME,
Util.prettifyText(en.getKey().toString())));
// Adjust icon
pib.icon(B2M.getOrDefault(en.getKey(), en.getKey()));
int count = ibc == null ? 0 : ibc.getBlockCounts().getOrDefault(en.getKey(), 0);
int value = en.getValue();
String color = count >= value ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
pib.description(color
+ user.getTranslation("island.limits.block-limit-syntax",
TextVariables.NUMBER, String.valueOf(count),
"[limit]", String.valueOf(value)));
result.add(pib.build());
}
}
@Override
public PanelItem getIcon() {
return new PanelItemBuilder().icon(Material.MAGENTA_GLAZED_TERRACOTTA).name(this.getName()).build();
}
@Override
public String getName() {
String sort = user.getTranslation(world, "island.limits.panel." + sortBy);
return user.getTranslation(world, "island.limits.panel.title-syntax", "[title]",
user.getTranslation(world, "limits.panel-title"), "[sort]", sort);
}
@Override
public List<@Nullable PanelItem> getPanelItems() {
return result;
}
@Override
public String getPermission() {
return "";
}
private String prettyNames(EntityGroup v) {
StringBuilder sb = new StringBuilder();
List<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,9 +1,12 @@
package world.bentobox.limits.commands; package world.bentobox.limits.commands.player;
import java.util.List; import java.util.List;
import java.util.Optional;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
/** /**
@ -45,7 +48,22 @@ public class PlayerCommand extends CompositeCommand {
showHelp(this, user); showHelp(this, user);
return false; return false;
} else { } else {
new LimitPanel(addon).showLimits(getWorld(), user, user.getUniqueId()); // Report the limit for the island, which is governed by the owner of the island
Optional<Island> opIsland = getIslands().getIslandAt(user.getLocation());
if (opIsland.isEmpty()) {
user.sendMessage("island.limits.errors.not-on-island");
return false;
}
Island island = opIsland.get();
if (!island.getWorld().equals(getWorld())) {
user.sendMessage("general.errors.wrong-world");
return false;
}
if (island.getOwner() == null) {
user.sendMessage("island.limits.errors.no-owner");
return false;
}
new LimitPanel(addon).showLimits((GameModeAddon) getAddon(), user, island.getOwner());
return true; return true;
} }
} }

View File

@ -1,10 +1,14 @@
package world.bentobox.limits.commands; package world.bentobox.limits.commands.player;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.calculators.Pipeliner;
/** /**
* *
@ -13,6 +17,7 @@ import world.bentobox.limits.Limits;
public class RecountCommand extends CompositeCommand { public class RecountCommand extends CompositeCommand {
private final Limits addon; private final Limits addon;
private @Nullable Island island;
/** /**
* Player command to do a recount. Has a cooldown * Player command to do a recount. Has a cooldown
@ -44,7 +49,8 @@ public class RecountCommand extends CompositeCommand {
showHelp(this, user); showHelp(this, user);
return false; return false;
} }
if (addon.getIslands().getIsland(getWorld(), user) == null) { island = addon.getIslands().getIsland(getWorld(), user);
if (island == null) {
user.sendMessage("general.errors.no-island"); user.sendMessage("general.errors.no-island");
return false; return false;
} }
@ -54,7 +60,17 @@ public class RecountCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) { public boolean execute(User user, String label, List<String> args) {
// Set cooldown // Set cooldown
setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120)); setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120));
new LimitsCalc(getWorld(), getPlugin(), user.getUniqueId(), addon, 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; 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.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

@ -15,6 +15,8 @@ import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; 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.configuration.ConfigurationSection;
import org.bukkit.event.Cancellable; import org.bukkit.event.Cancellable;
import org.bukkit.event.Event; import org.bukkit.event.Event;
@ -28,6 +30,7 @@ import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFadeEvent; import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockFormEvent; import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockGrowEvent;
import org.bukkit.event.block.BlockMultiPlaceEvent; import org.bukkit.event.block.BlockMultiPlaceEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.BlockSpreadEvent; import org.bukkit.event.block.BlockSpreadEvent;
@ -36,12 +39,14 @@ import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
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.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.objects.IslandBlockCount; import world.bentobox.limits.objects.IslandBlockCount;
@ -83,7 +88,7 @@ public class BlockLimitsListener implements Listener {
handler.loadObjects().forEach(ibc -> { handler.loadObjects().forEach(ibc -> {
// Clean up // Clean up
if (addon.isCoveredGameMode(ibc.getGameMode())) { if (addon.isCoveredGameMode(ibc.getGameMode())) {
ibc.getBlockCount().keySet().removeIf(DO_NOT_COUNT::contains); ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains);
// Store // Store
islandCountMap.put(ibc.getUniqueId(), ibc); islandCountMap.put(ibc.getUniqueId(), ibc);
} else { } else {
@ -146,7 +151,7 @@ public class BlockLimitsListener implements Listener {
* Save the count database completely * Save the count database completely
*/ */
public void save() { public void save() {
islandCountMap.values().forEach(handler::saveObject); islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync);
} }
// Player-related events // Player-related events
@ -162,29 +167,26 @@ public class BlockLimitsListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onTurtleEggBreak(PlayerInteractEvent e) { public void onTurtleEggBreak(PlayerInteractEvent e) {
if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) { if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) {
handleBreak(e, e.getClickedBlock()); handleBreak(e, e.getClickedBlock());
} }
} }
private void handleBreak(Event e, Block b) { private void handleBreak(Event e, Block b) {
Material mat = b.getType(); if (!addon.inGameModeWorld(b.getWorld())) {
// Special handling for crops that can break in different ways return;
if (mat.equals(Material.WHEAT_SEEDS)) {
mat = Material.WHEAT;
} else if (mat.equals(Material.BEETROOT_SEEDS)) {
mat = Material.BEETROOT;
} }
Material mat = b.getType();
// Check for stackable plants // Check for stackable plants
if (STACKABLE.contains(b.getType())) { if (STACKABLE.contains(b.getType())) {
// Check for blocks above // Check for blocks above
Block block = b; Block block = b;
while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) { while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) {
block = block.getRelative(BlockFace.UP); block = block.getRelative(BlockFace.UP);
process(block, false, mat); process(block, false);
} }
} }
process(b, false, mat); process(b, false);
// Player breaks a block and there was a redstone dust/repeater/... above // 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) { 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); process(b.getRelative(BlockFace.UP), false);
@ -208,6 +210,13 @@ public class BlockLimitsListener implements Listener {
notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType()); 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) { private void notify(Cancellable e, User user, int limit, Material m) {
if (limit > -1) { if (limit > -1) {
user.notify("block-limits.hit-limit", user.notify("block-limits.hit-limit",
@ -248,6 +257,16 @@ public class BlockLimitsListener implements Listener {
process(e.getBlock(), true); process(e.getBlock(), true);
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockGrowEvent e) {
if (process(e.getNewState().getBlock(), true) > -1) {
e.setCancelled(true);
e.getBlock().getWorld().getBlockAt(e.getBlock().getLocation()).setBlockData(e.getBlock().getBlockData());
} else {
process(e.getBlock(), false);
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(LeavesDecayEvent e) { public void onBlock(LeavesDecayEvent e) {
process(e.getBlock(), false); process(e.getBlock(), false);
@ -278,28 +297,37 @@ public class BlockLimitsListener implements Listener {
} }
} }
private int process(Block b, boolean add) { /**
return process(b, add, b.getType()); * 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();
// Return equivalents. if (mat == Material.REDSTONE_WALL_TORCH) {
public Material fixMaterial(Material b) {
if (b == Material.REDSTONE_WALL_TORCH) {
return Material.REDSTONE_TORCH; return Material.REDSTONE_TORCH;
} else if (b == Material.WALL_TORCH) { } else if (mat == Material.WALL_TORCH) {
return Material.TORCH; return Material.TORCH;
} else if (b == Material.ZOMBIE_WALL_HEAD) { } else if (mat == Material.ZOMBIE_WALL_HEAD) {
return Material.ZOMBIE_HEAD; return Material.ZOMBIE_HEAD;
} else if (b == Material.CREEPER_WALL_HEAD) { } else if (mat == Material.CREEPER_WALL_HEAD) {
return Material.CREEPER_HEAD; return Material.CREEPER_HEAD;
} else if (b == Material.PLAYER_WALL_HEAD) { } else if (mat == Material.PLAYER_WALL_HEAD) {
return Material.PLAYER_HEAD; return Material.PLAYER_HEAD;
} else if (b == Material.DRAGON_WALL_HEAD) { } else if (mat == Material.DRAGON_WALL_HEAD) {
return Material.DRAGON_HEAD; return Material.DRAGON_HEAD;
} else if (b != null && b == Material.getMaterial("BAMBOO_SAPLING")) { } else if (mat == Material.BAMBOO_SAPLING) {
return Material.getMaterial("BAMBOO"); return Material.BAMBOO;
} else if (mat == Material.PISTON_HEAD || mat == Material.MOVING_PISTON) {
TechnicalPiston tp = (TechnicalPiston) b;
if (tp.getType() == TechnicalPiston.Type.NORMAL) {
return Material.PISTON;
} else {
return Material.STICKY_PISTON;
} }
return b; }
return mat;
} }
/** /**
@ -307,11 +335,10 @@ public class BlockLimitsListener implements Listener {
* *
* @param b - block * @param b - block
* @param add - true to add a block, false to remove * @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 * @return limit amount if over limit, or -1 if no limitation
*/ */
private int process(Block b, boolean add, Material changeTo) { private int process(Block b, boolean add) {
if (DO_NOT_COUNT.contains(fixMaterial(b.getType())) || !addon.inGameModeWorld(b.getWorld())) { if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) {
return -1; return -1;
} }
// Check if on island // Check if on island
@ -322,40 +349,54 @@ public class BlockLimitsListener implements Listener {
// Invalid world // Invalid world
return -1; 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)); islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode));
saveMap.putIfAbsent(id, 0);
if (add) { if (add) {
// Check limit // Check limit
int limit = checkLimit(b.getWorld(), fixMaterial(b.getType()), id); int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id);
if (limit > -1) { if (limit > -1) {
return limit; return limit;
} }
islandCountMap.get(id).add(fixMaterial(b.getType())); islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
saveMap.merge(id, 1, Integer::sum);
} else { } else {
if (islandCountMap.containsKey(id)) { if (islandCountMap.containsKey(id)) {
// Check for changes islandCountMap.get(id).remove(fixMaterial(b.getBlockData()));
Material fixed = fixMaterial(changeTo);
if (!fixed.equals(fixMaterial(b.getType())) && fixed.isBlock() && !DO_NOT_COUNT.contains(fixed)) {
// Check limit
int limit = checkLimit(b.getWorld(), fixed, id);
if (limit > -1) {
return limit;
}
islandCountMap.get(id).add(fixed);
}
islandCountMap.get(id).remove(fixMaterial(b.getType()));
saveMap.merge(id, 1, Integer::sum);
} }
} }
if (saveMap.get(id) > CHANGE_LIMIT) { updateSaveMap(id);
handler.saveObject(islandCountMap.get(id));
saveMap.remove(id);
}
return -1; return -1;
}).orElse(-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 * Check if this material is at its limit for world on this island
* *
@ -366,18 +407,18 @@ public class BlockLimitsListener implements Listener {
*/ */
private int checkLimit(World w, Material m, String id) { private int checkLimit(World w, Material m, String id) {
// Check island limits // Check island limits
IslandBlockCount island = islandCountMap.get(id); IslandBlockCount ibc = islandCountMap.get(id);
if (island.isBlockLimited(m)) { if (ibc.isBlockLimited(m)) {
return island.isAtLimit(m) ? island.getBlockLimit(m) : -1; return ibc.isAtLimit(m) ? ibc.getBlockLimit(m) + ibc.getBlockLimitOffset(m) : -1;
} }
// Check specific world limits // Check specific world limits
if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) { if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) {
// Material is overridden in world // 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 // Check default limit map
if (defaultLimitMap.containsKey(m) && island.isAtLimit(m, defaultLimitMap.get(m))) { if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) {
return defaultLimitMap.get(m); return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m);
} }
// No limit // No limit
return -1; return -1;
@ -394,14 +435,19 @@ public class BlockLimitsListener implements Listener {
// Merge limits // Merge limits
Map<Material, Integer> result = new EnumMap<>(Material.class); Map<Material, Integer> result = new EnumMap<>(Material.class);
// Default // Default
defaultLimitMap.forEach(result::put); result.putAll(defaultLimitMap);
// World // World
if (worldLimitMap.containsKey(w)) { if (worldLimitMap.containsKey(w)) {
worldLimitMap.get(w).forEach(result::put); result.putAll(worldLimitMap.get(w));
} }
// Island // Island
if (islandCountMap.containsKey(id)) { 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; return result;
} }
@ -428,7 +474,7 @@ public class BlockLimitsListener implements Listener {
*/ */
public void setIsland(String islandId, IslandBlockCount ibc) { public void setIsland(String islandId, IslandBlockCount ibc) {
islandCountMap.put(islandId, ibc); islandCountMap.put(islandId, ibc);
handler.saveObject(ibc); handler.saveObjectAsync(ibc);
} }
/** /**
@ -442,4 +488,14 @@ public class BlockLimitsListener implements Listener {
return islandCountMap.get(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

@ -1,28 +1,53 @@
package world.bentobox.limits.listeners; 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.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Breedable;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import org.bukkit.event.entity.EntityBreedEvent;
import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.vehicle.VehicleCreateEvent; 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.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
import world.bentobox.limits.EntityGroup;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.Settings;
import world.bentobox.limits.objects.IslandBlockCount;
public class EntityLimitListener implements Listener { public class EntityLimitListener implements Listener {
private static final String MOD_BYPASS = "mod.bypass"; private static final String MOD_BYPASS = "mod.bypass";
private final Limits addon; 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 * Handles entity and natural limitations
@ -39,76 +64,65 @@ public class EntityLimitListener implements Listener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onMinecart(VehicleCreateEvent e) { public void onMinecart(VehicleCreateEvent e) {
// Return if not in a known world // Return if not in a known world
if (!addon.getPlugin().getIWM().inWorld(e.getVehicle().getWorld())) { if (!addon.inGameModeWorld(e.getVehicle().getWorld())) {
return;
}
// Debounce
if (justSpawned.contains(e.getVehicle().getUniqueId())) {
justSpawned.remove(e.getVehicle().getUniqueId());
return; return;
} }
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : Objects.requireNonNull(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 // Check island
addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation()).ifPresent(island -> { addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation())
// Ignore spawn // Ignore spawn
if (island.isSpawn()) { .filter(i -> !i.isSpawn())
return; .ifPresent(island -> {
}
// Check if the player is at the limit // Check if the player is at the limit
if (!bypass && atLimit(island, e.getVehicle())) { AtLimitResult res = atLimit(island, e.getVehicle());
if (res.hit()) {
e.setCancelled(true); e.setCancelled(true);
for (Entity ent : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) { this.tellPlayers(e.getVehicle().getLocation(), e.getVehicle(), SpawnReason.MOUNT, res);
if (ent instanceof Player) {
((Player) ent).updateInventory();
User.getInstance(ent).notify("entity-limits.hit-limit", "[entity]",
Util.prettifyText(e.getVehicle().getType().toString())
, TextVariables.NUMBER, String.valueOf(addon.getSettings().getLimits().get(e.getVehicle().getType())));
}
}
} }
}); });
} }
@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) @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCreatureSpawn(final CreatureSpawnEvent e) { public void onCreatureSpawn(final CreatureSpawnEvent e) {
// Return if not in a known world // Return if not in a known world
if (!addon.getPlugin().getIWM().inWorld(e.getLocation())) { if (!addon.inGameModeWorld(e.getLocation().getWorld())) {
return; return;
} }
boolean bypass = false; if (justSpawned.contains(e.getEntity().getUniqueId())) {
// Check why it was spawned justSpawned.remove(e.getEntity().getUniqueId());
switch (e.getSpawnReason()) { return;
// 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:
bypass = checkByPass(e.getLocation());
break;
default:
// Other natural reasons
break;
} }
// Tag the entity with the island spawn location if (e.getSpawnReason().equals(SpawnReason.SHOULDER_ENTITY) || (!(e.getEntity() instanceof Villager ) && e.getSpawnReason().equals(SpawnReason.BREEDING))) {
checkLimit(e, bypass); // 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);
} }
private boolean checkByPass(Location l) {
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : Objects.requireNonNull(l.getWorld()).getNearbyEntities(l, 5, 5, 5)) {
if (entity instanceof Player) {
Player player = (Player)entity;
if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(l.getWorld()) + MOD_BYPASS)) {
return true;
}
}
}
return false;
} }
/** /**
@ -117,41 +131,234 @@ public class EntityLimitListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlock(HangingPlaceEvent e) { public void onBlock(HangingPlaceEvent e) {
if (!addon.inGameModeWorld(e.getBlock().getWorld())) {
return;
}
Player player = e.getPlayer(); Player player = e.getPlayer();
if (player == null) return; if (player == null) return;
addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> { 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); boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
// Check if entity can be hung // Check if entity can be hung
if (!bypass && !island.isSpawn() && atLimit(island, e.getEntity())) { AtLimitResult res;
if (!bypass && !island.isSpawn() && (res = atLimit(island, e.getEntity())).hit()) {
// Not allowed // Not allowed
e.setCancelled(true); e.setCancelled(true);
if (res.getTypelimit() != null) {
User.getInstance(player).notify("block-limits.hit-limit", "[material]", User.getInstance(player).notify("block-limits.hit-limit", "[material]",
Util.prettifyText(e.getEntity().getType().toString()), Util.prettifyText(e.getEntity().getType().toString()),
TextVariables.NUMBER, String.valueOf(addon.getSettings().getLimits().getOrDefault(e.getEntity().getType(), -1))); 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()));
}
} }
}); });
} }
private void checkLimit(CreatureSpawnEvent e, boolean bypass) { /**
addon.getIslands().getIslandAt(e.getLocation()).ifPresent(island -> { * Check if a creature is allowed to spawn or not
* @param e - CreatureSpawnEvent
* @param async - true if check can be done async, false if not
* @return true if allowed or asycn, false if not.
*/
private boolean checkLimit(Cancellable c, LivingEntity e, SpawnReason reason, boolean async) {
Location l = e.getLocation();
if (async) {
c.setCancelled(true);
}
return processIsland(c, e, l, reason, async);
}
private boolean processIsland(Cancellable c, LivingEntity e, Location l, SpawnReason reason, boolean async) {
if (addon.getIslands().getIslandAt(e.getLocation()).isEmpty()) {
c.setCancelled(false);
return true;
}
Island island = addon.getIslands().getIslandAt(e.getLocation()).get();
// Check if creature is allowed to spawn or not // Check if creature is allowed to spawn or not
if (!bypass && !island.isSpawn() && atLimit(island, e.getEntity())) { AtLimitResult res = atLimit(island, e);
// Not allowed if (island.isSpawn() || !res.hit()) {
e.setCancelled(true); // 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 // If the reason is anything but because of a spawner then tell players within range
if (!e.getSpawnReason().equals(SpawnReason.SPAWNER) && !e.getSpawnReason().equals(SpawnReason.NATURAL) && !e.getSpawnReason().equals(SpawnReason.INFECTION) && !e.getSpawnReason().equals(SpawnReason.NETHER_PORTAL) && !e.getSpawnReason().equals(SpawnReason.REINFORCEMENTS) && !e.getSpawnReason().equals(SpawnReason.SLIME_SPLIT)) { tellPlayers(l, e, reason, res);
World w = e.getLocation().getWorld(); return false;
if (w == null) return; }
for (Entity ent : w.getNearbyEntities(e.getLocation(), 5, 5, 5)) { return true;
if (ent instanceof Player) { }
User.getInstance(ent).notify("entity-limits.hit-limit", "[entity]",
Util.prettifyText(e.getEntityType().toString()), private void preSpawn(EntityType entityType, SpawnReason reason, Location l) {
TextVariables.NUMBER, String.valueOf(addon.getSettings().getLimits().get(e.getEntityType())));
// Check for entities that need cleanup
switch (reason) {
case BUILD_IRONGOLEM -> detectIronGolem(l);
case BUILD_SNOWMAN -> detectSnowman(l);
case BUILD_WITHER -> {
detectWither(l);
}
default -> throw new IllegalArgumentException("Unexpected value: " + reason);
}
Entity entity = l.getWorld().spawnEntity(l, entityType);
justSpawned.add(entity.getUniqueId());
if (reason == SpawnReason.BUILD_WITHER) {
// Create explosion
l.getWorld().createExplosion(l, 7F, true, true, entity);
}
}
private void detectIronGolem(Location l) {
Block legs = l.getBlock();
// Erase legs
addon.getBlockLimitListener().removeBlock(legs);
legs.setType(Material.AIR);
// Look around for possible constructions
for (BlockFace bf : CARDINALS) {
Block body = legs.getRelative(bf);
if (body.getType().equals(Material.IRON_BLOCK)) {
// Check for head
Block head = body.getRelative(bf);
if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
// Check for arms the rule is that they must be opposite and have nothing "beneath" them
for (BlockFace bf2 : CARDINALS) {
Block arm1 = body.getRelative(bf2);
Block arm2 = body.getRelative(bf2.getOppositeFace());
if (arm1.getType() == Material.IRON_BLOCK && arm2.getType() == Material.IRON_BLOCK
&& arm1.getRelative(bf.getOppositeFace()).isEmpty()
&& arm2.getRelative(bf.getOppositeFace()).isEmpty()) {
// Erase!
addon.getBlockLimitListener().removeBlock(body);
addon.getBlockLimitListener().removeBlock(arm1);
addon.getBlockLimitListener().removeBlock(arm2);
addon.getBlockLimitListener().removeBlock(head);
body.setType(Material.AIR);
arm1.setType(Material.AIR);
arm2.setType(Material.AIR);
head.setType(Material.AIR);
return;
}
}
}
} }
} }
} }
private void detectSnowman(Location l) {
Block legs = l.getBlock();
// Erase legs
addon.getBlockLimitListener().removeBlock(legs);
legs.setType(Material.AIR);
// Look around for possible constructions
for (BlockFace bf : CARDINALS) {
Block body = legs.getRelative(bf);
if (body.getType().equals(Material.SNOW_BLOCK)) {
// Check for head
Block head = body.getRelative(bf);
if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
// Erase
addon.getBlockLimitListener().removeBlock(body);
addon.getBlockLimitListener().removeBlock(head);
body.setType(Material.AIR);
head.setType(Material.AIR);
return;
}
}
}
}
private void detectWither(Location l) {
Block legs = l.getBlock();
// Erase legs
addon.getBlockLimitListener().removeBlock(legs);
legs.setType(Material.AIR);
// Look around for possible constructions
for (BlockFace bf : CARDINALS) {
Block body = legs.getRelative(bf);
if (isWither(body)) {
// Check for head
Block head = body.getRelative(bf);
if (head.getType().equals(Material.WITHER_SKELETON_SKULL) || head.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) {
// Check for arms the rule is that they must be opposite and have nothing "beneath" them
for (BlockFace bf2 : CARDINALS) {
Block arm1 = body.getRelative(bf2);
Block arm2 = body.getRelative(bf2.getOppositeFace());
Block head2 = arm1.getRelative(bf);
Block head3 = arm2.getRelative(bf);
if (isWither(arm1)
&& isWither(arm2)
&& arm1.getRelative(bf.getOppositeFace()).isEmpty()
&& arm2.getRelative(bf.getOppositeFace()).isEmpty()
&& (head2.getType().equals(Material.WITHER_SKELETON_SKULL) || head2.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
&& (head3.getType().equals(Material.WITHER_SKELETON_SKULL) || head3.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
) {
// Erase!
addon.getBlockLimitListener().removeBlock(body);
addon.getBlockLimitListener().removeBlock(arm1);
addon.getBlockLimitListener().removeBlock(arm2);
addon.getBlockLimitListener().removeBlock(head);
addon.getBlockLimitListener().removeBlock(head2);
addon.getBlockLimitListener().removeBlock(head3);
body.setType(Material.AIR);
arm1.setType(Material.AIR);
arm2.setType(Material.AIR);
head.setType(Material.AIR);
head2.setType(Material.AIR);
head3.setType(Material.AIR);
return;
}
}
}
}
}
}
private boolean isWither(Block body) {
if (Util.getMinecraftVersion() < 16) {
return body.getType().equals(Material.SOUL_SAND);
}
return Tag.WITHER_SUMMON_BASE_BLOCKS.isTagged(body.getType());
}
/**
* Tell players within a 5 x 5 x 5 radius that the spawning was denied. Informing happens 1 tick after event
* @param l location
* @param entity entity spawned
* @param reason reason - some reasons are not reported
* @param res at limit result
*/
private void tellPlayers(Location l, Entity entity, SpawnReason reason, AtLimitResult res) {
if (reason.equals(SpawnReason.SPAWNER) || reason.equals(SpawnReason.NATURAL)
|| reason.equals(SpawnReason.INFECTION) || reason.equals(SpawnReason.NETHER_PORTAL)
|| reason.equals(SpawnReason.REINFORCEMENTS) || reason.equals(SpawnReason.SLIME_SPLIT)) {
return;
}
World w = l.getWorld();
if (w == null) return;
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> {
for (Entity ent : w.getNearbyEntities(l, 5, 5, 5)) {
if (ent instanceof Player p) {
p.updateInventory();
if (res.getTypelimit() != null) {
User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
Util.prettifyText(entity.getType().toString()),
TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue()));
} else {
User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")",
TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue()));
}
}
} }
}); });
@ -163,21 +370,104 @@ public class EntityLimitListener implements Listener {
* @param ent - the entity * @param ent - the entity
* @return true if at the limit, false if not * @return true if at the limit, false if not
*/ */
private boolean atLimit(Island island, Entity ent) { AtLimitResult atLimit(Island island, Entity ent) {
// Check island settings first // Check island settings first
int limitAmount = -1; int limitAmount = -1;
if (addon.getBlockLimitListener().getIsland(island.getUniqueId()) != null) { Map<EntityGroup, Integer> groupsLimits = new HashMap<>();
limitAmount = addon.getBlockLimitListener().getIsland(island.getUniqueId()).getEntityLimit(ent.getType());
@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<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 no island settings then try global settings
if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) { if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) {
limitAmount = addon.getSettings().getLimits().get(ent.getType()); limitAmount = addon.getSettings().getLimits().get(ent.getType());
} }
if (limitAmount < 0) return false; // 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 // We have to count the entities
return ent.getWorld().getEntities().stream() if (limitAmount >= 0)
{
int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream()
.filter(e -> e.getType().equals(ent.getType())) .filter(e -> e.getType().equals(ent.getType()))
.filter(e -> island.inIslandSpace(e.getLocation())).count() >= limitAmount; .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<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

@ -5,7 +5,6 @@ import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@ -17,16 +16,21 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.permissions.PermissionAttachmentInfo; 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;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason; import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent; import world.bentobox.bentobox.api.events.team.TeamSetownerEvent;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.limits.EntityGroup;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.events.LimitsJoinPermCheckEvent;
import world.bentobox.limits.events.LimitsPermCheckEvent;
import world.bentobox.limits.objects.IslandBlockCount; import world.bentobox.limits.objects.IslandBlockCount;
/** /**
* Sets block limits based on player permission * Sets block limits based on player permission
*
* @author tastybento * @author tastybento
* *
*/ */
@ -38,63 +42,132 @@ public class JoinListener implements Listener {
this.addon = addon; this.addon = addon;
} }
private void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) { /**
* 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); IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
// Check permissions
if (ibc != null) { if (ibc != null) {
// Clear permission limits // Clear permission limits
ibc.getEntityLimits().clear(); ibc.getEntityLimits().clear();
ibc.getEntityGroupLimits().clear();
ibc.getBlockLimits().clear(); ibc.getBlockLimits().clear();
} }
for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) { for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) {
if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix)) continue; if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix)
// No wildcards || badSyntaxCheck(perms, player.getName(), permissionPrefix)) {
if (perms.getPermission().contains(permissionPrefix + "*")) { continue;
logError(player.getName(), perms.getPermission(), "wildcards are not allowed.");
return;
} }
// Check formatting // Check formatting
String[] split = perms.getPermission().split("\\."); String[] split = perms.getPermission().split("\\.");
if (split.length != 5) {
logError(player.getName(), perms.getPermission(), "format must be '" + permissionPrefix + "MATERIAL.NUMBER' or '" + permissionPrefix + "ENTITY-TYPE.NUMBER'");
return;
}
// Check value
if (!NumberUtils.isDigits(split[4])) {
logError(player.getName(), perms.getPermission(), "the last part MUST be a number!");
return;
}
// Entities & materials // Entities & materials
EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst().orElse(null); EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3]))
Material m = Arrays.stream(Material.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst().orElse(null); .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 (et == null && m == 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."); logError(player.getName(), perms.getPermission(),
split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material or entity type/group.");
break; break;
} }
// Make an ibc if required // Make an ibc if required
if (ibc == null) { if (ibc == null) {
ibc = new IslandBlockCount(islandId, gameMode); ibc = new IslandBlockCount(islandId, gameMode);
} }
if (et != null && m == null) { // Get the value
// Entity limit int value = Integer.parseInt(split[4]);
ibc.setEntityLimit(et, Math.max(ibc.getEntityLimit(et), Integer.valueOf(split[4]))); addon.log("Setting login limit via perm for " + player.getName() + "...");
} else if (m != null && et == null) {
// Material limit // Fire perm check event
ibc.setBlockLimit(m, Math.max(ibc.getBlockLimit(m), Integer.valueOf(split[4]))); LimitsPermCheckEvent l = new LimitsPermCheckEvent(player, islandId, ibc, entgroup, et, m, value);
} else { Bukkit.getPluginManager().callEvent(l);
if (m.isBlock()) { if (l.isCancelled()) {
// Material limit addon.log("Permissions not set because another addon/plugin canceled setting.");
ibc.setBlockLimit(m, Math.max(ibc.getBlockLimit(m), Integer.valueOf(split[4]))); continue;
} else {
// This is an entity setting
ibc.setEntityLimit(et, Math.max(ibc.getEntityLimit(et), Integer.valueOf(split[4])));
} }
// 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 // 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);
}
}
// 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 void logError(String name, String perm, String error) { private void logError(String name, String perm, String error) {
@ -107,8 +180,7 @@ public class JoinListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNewIsland(IslandEvent e) { public void onNewIsland(IslandEvent e) {
if (!e.getReason().equals(Reason.CREATED) if (!e.getReason().equals(Reason.CREATED) && !e.getReason().equals(Reason.RESETTED)
&& !e.getReason().equals(Reason.RESETTED)
&& !e.getReason().equals(Reason.REGISTERED)) { && !e.getReason().equals(Reason.REGISTERED)) {
return; return;
} }
@ -121,16 +193,46 @@ public class JoinListener implements Listener {
setOwnerPerms(e.getIsland(), e.getNewOwner()); setOwnerPerms(e.getIsland(), e.getNewOwner());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent e) { public void onPlayerJoin(PlayerJoinEvent e) {
// Check if player has any islands in the game modes // Check if player has any islands in the game modes
addon.getGameModes().forEach(gm -> { addon.getGameModes().forEach(gm -> {
if (addon.getIslands().hasIsland(gm.getOverWorld(), e.getPlayer().getUniqueId())) { addon.getIslands().getIslands(gm.getOverWorld(), e.getPlayer().getUniqueId()).stream()
String islandId = addon.getIslands().getIsland(gm.getOverWorld(), e.getPlayer().getUniqueId()).getUniqueId(); .filter(island -> e.getPlayer().getUniqueId().equals(island.getOwner()))
checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId, gm.getDescription().getName()); .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) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -165,7 +267,8 @@ public class JoinListener implements Listener {
String prefix = addon.getGameModePermPrefix(world); String prefix = addon.getGameModePermPrefix(world);
String name = addon.getGameModeName(world); String name = addon.getGameModeName(world);
if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) { if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) {
checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.", island.getUniqueId(), name); checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.",
island.getUniqueId(), name);
} }
} }
} }

View File

@ -7,11 +7,13 @@ import java.util.UUID;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
/** /**
* @author tastybento * @author tastybento
* *
*/ */
@Table(name = "EntityLimits")
public class EntityLimitsDO implements DataObject { public class EntityLimitsDO implements DataObject {
@Expose @Expose
@ -78,10 +80,9 @@ public class EntityLimitsDO implements DataObject {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (!(obj instanceof EntityLimitsDO)) { if (!(obj instanceof EntityLimitsDO other)) {
return false; return false;
} }
EntityLimitsDO other = (EntityLimitsDO) obj;
if (uniqueId == null) { if (uniqueId == null) {
return other.uniqueId == null; return other.uniqueId == null;
} else return uniqueId.equals(other.uniqueId); } else return uniqueId.equals(other.uniqueId);

View File

@ -1,7 +1,9 @@
package world.bentobox.limits.objects; package world.bentobox.limits.objects;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
@ -9,139 +11,209 @@ import org.bukkit.entity.EntityType;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
/** /**
* @author tastybento * @author tastybento
* *
*/ */
@Table(name = "IslandBlockCount")
public class IslandBlockCount implements DataObject { public class IslandBlockCount implements DataObject {
@Expose @Expose
private String uniqueId = ""; private Map<Material, Integer> blockCounts = new EnumMap<>(Material.class);
@Expose
private String gameMode = "";
@Expose
private Map<Material, Integer> blockCount = new EnumMap<>(Material.class);
/** /**
* Permission based limits * Permission based limits
*/ */
@Expose @Expose
private Map<Material, Integer> blockLimits = new EnumMap<>(Material.class); 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 @Expose
private Map<EntityType, Integer> entityLimits = new EnumMap<>(EntityType.class); 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;
// Required for YAML database /**
public IslandBlockCount() {} * Create an island block count object
*
public IslandBlockCount(String uniqueId2, String gameMode2) { * @param islandId - unique Island ID string
this.uniqueId = uniqueId2; * @param gameMode - Game mode name from gm.getDescription().getName()
this.gameMode = gameMode2; */
public IslandBlockCount(String islandId, String gameMode) {
this.uniqueId = islandId;
this.gameMode = gameMode;
setChanged();
} }
/* (non-Javadoc) /**
* @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() * Add a material to the count
*
* @param material - material
*/ */
@Override public void add(Material material) {
public String getUniqueId() { getBlockCounts().merge(material, 1, Integer::sum);
return uniqueId; setChanged();
} }
/* (non-Javadoc) /**
* @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String) * Clear all island-specific entity group limits
*/ */
@Override public void clearEntityGroupLimits() {
public void setUniqueId(String uniqueId) { entityGroupLimits.clear();
this.uniqueId = uniqueId; 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 * @return the blockCount
*/ */
public Map<Material, Integer> getBlockCount() { public Map<Material, Integer> getBlockCounts() {
return blockCount; if (blockCounts == null) {
blockCounts = new EnumMap<>(Material.class);
}
return blockCounts;
} }
/** /**
* @param blockCount the blockCount to set * Get the block limit for this material for this island
*/ *
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 * @param m - material
* @return true if no more material can be added * @return limit or -1 for unlimited
*/ */
public boolean isAtLimit(Material m) { public int getBlockLimit(Material m) {
// Check island limits first return getBlockLimits().getOrDefault(m, -1);
return blockLimits.containsKey(m) && blockCount.getOrDefault(m, 0) >= blockLimits.get(m);
} }
public boolean isBlockLimited(Material m) { /**
return blockLimits.containsKey(m); * 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 * @return the blockLimits
*/ */
public Map<Material, Integer> getBlockLimits() { public Map<Material, Integer> getBlockLimits() {
return blockLimits; return Objects.requireNonNullElse(blockLimits, new EnumMap<>(Material.class));
} }
/** /**
* @param blockLimits the blockLimits to set * @return the blockLimitsOffset
*/ */
public void setBlockLimits(Map<Material, Integer> blockLimits) { public Map<Material, Integer> getBlockLimitsOffset() {
this.blockLimits = blockLimits; if (blockLimitsOffset == null) {
blockLimitsOffset = new EnumMap<>(Material.class);
}
return blockLimitsOffset;
} }
/** /**
* Get the block limit for this material for this island * Get the limit for an entity group
* @param m - material *
* @param name - entity group
* @return limit or -1 for unlimited * @return limit or -1 for unlimited
*/ */
public Integer getBlockLimit(Material m) { public int getEntityGroupLimit(String name) {
return blockLimits.getOrDefault(m, -1); return getEntityGroupLimits().getOrDefault(name, -1);
} }
/** /**
* Set the block limit for this material for this island * Get the offset for an entity group
* @param m - material *
* @param limit - maximum number allowed * @param name - entity group
* @return offset
*/ */
public void setBlockLimit(Material m, int limit) { public int getEntityGroupLimitOffset(String name) {
blockLimits.put(m, limit); 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;
} }
/** /**
@ -151,22 +223,156 @@ public class IslandBlockCount implements DataObject {
return gameMode; 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) { public boolean isGameMode(String gameMode) {
return this.gameMode.equals(gameMode); return getGameMode().equals(gameMode);
} }
/** /**
* @param gameMode the gameMode to set * Remove a material from the count
*
* @param material - material
*/ */
public void setGameMode(String gameMode) { public void remove(Material material) {
this.gameMode = gameMode; getBlockCounts().put(material, getBlockCounts().getOrDefault(material, 0) - 1);
getBlockCounts().values().removeIf(v -> v <= 0);
setChanged();
} }
/** /**
* @return the entityLimits * @param blockCounts the blockCount to set
*/ */
public Map<EntityType, Integer> getEntityLimits() { public void setBlockCounts(Map<Material, Integer> blockCounts) {
return entityLimits; 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();
} }
/** /**
@ -174,31 +380,38 @@ public class IslandBlockCount implements DataObject {
*/ */
public void setEntityLimits(Map<EntityType, Integer> entityLimits) { public void setEntityLimits(Map<EntityType, Integer> entityLimits) {
this.entityLimits = entityLimits; this.entityLimits = entityLimits;
setChanged();
} }
/** /**
* Set an island-specific entity type limit * Set an offset to an entity limit. This will increase/decrease the value of
* @param t - entity type * the limit.
* @param limit - limit *
* @param type Entity Type
* @param entityLimitsOffset the entityLimitsOffset to set
*/ */
public void setEntityLimit(EntityType t, int limit) { public void setEntityLimitsOffset(EntityType type, Integer entityLimitsOffset) {
entityLimits.put(t, limit); this.getEntityLimitsOffset().put(type, entityLimitsOffset);
} }
/** /**
* Get the limit for an entity type * @param gameMode the gameMode to set
* @param t - entity type
* @return limit or -1 for unlimited
*/ */
public int getEntityLimit(EntityType t) { public void setGameMode(String gameMode) {
return entityLimits.getOrDefault(t, -1); this.gameMode = gameMode;
setChanged();
} }
/** /*
* Clear all island-specific entity type limits * (non-Javadoc)
*
* @see
* world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.
* String)
*/ */
public void clearEntityLimits() { @Override
entityLimits.clear(); public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
setChanged();
} }
} }

View File

@ -1,37 +1,22 @@
name: Limits name: Limits
main: world.bentobox.limits.Limits main: world.bentobox.limits.Limits
version: ${version}${build.number} version: ${version}${build.number}
api-version: 1.12 api-version: 2.7.1
authors: tastybento authors: tastybento
softdepend: AcidIsland, BSkyBlock, CaveBlock softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid
permissions: permissions:
acidisland.limits.player.limits: '[gamemode].limits.player.limits':
description: Player can use limits command description: Player can use limits command
default: true default: true
acidisland.limits.player.recount: '[gamemode].limits.player.recount':
description: Player can use recount command description: Player can use recount command
default: true default: true
acidisland.limits.admin.limits: '[gamemode].limits.admin.limits':
description: Player can use admin limits command description: Player can use admin limits command
default: op default: op
bskyblock.limits.player.limits: '[gamemode].mod.bypass':
description: Player can use limits command description: Player can bypass limits
default: true
bskyblock.limits.player.recount:
description: Player can use recount command
default: true
bskyblock.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.player.recount:
description: Player can use recount command
default: true
caveblock.limits.admin.limits:
description: Player can use admin limits command
default: op default: op

View File

@ -2,7 +2,11 @@
gamemodes: gamemodes:
- AcidIsland - AcidIsland
- BSkyBlock - 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 # Permissions
# Island owners can be given permissions that override all general settings # Island owners can be given permissions that override all general settings
@ -13,6 +17,9 @@ gamemodes:
# Cooldown for player recount command in seconds # Cooldown for player recount command in seconds
cooldown: 120 cooldown: 120
# Use async checks for snowmen and iron golums. Set to false if you see problems.
async-golums: true
# General block limiting # General block limiting
# Use this section to limit how many blocks can be added to an island. # Use this section to limit how many blocks can be added to an island.
# 0 means the item will be blocked from placement completely. # 0 means the item will be blocked from placement completely.
@ -33,5 +40,91 @@ worlds:
entitylimits: entitylimits:
ENDERMAN: 5 ENDERMAN: 5
CHICKEN: 10 CHICKEN: 10
pig_ZOMbIe: 15
# Entity Groups
# Name the group anything you like
# Select an icon, which is a Bukkit Material
# Select the limit for the total group
# List the entities in the group using Bukkit EntityTypes
entitygrouplimits:
Monsters:
icon: ROTTEN_FLESH
limit: 250
entities:
- SKELETON
- SILVERFISH
- STRAY
- ZOMBIE_VILLAGER
- WITHER
- WARDEN
- BLAZE
- DROWNED
- BREEZE
- ZOMBIFIED_PIGLIN
- EVOKER
- PILLAGER
- HUSK
- CREEPER
- VINDICATOR
- ZOMBIE
- ENDERMAN
- ELDER_GUARDIAN
- WITHER_SKELETON
- CAVE_SPIDER
- GUARDIAN
- RAVAGER
- PIGLIN
- BOGGED
- WITCH
- ENDERMITE
- ZOGLIN
- PIGLIN_BRUTE
- ILLUSIONER
- SPIDER
- VEX
Animals:
icon: SADDLE
limit: 200
entities:
- SHEEP
- AXOLOTL
- DONKEY
- MOOSHROOM
- TRADER_LLAMA
- BEE
- HORSE
- ZOMBIE_HORSE
- PIG
- PARROT
- CHICKEN
- RABBIT
- SNIFFER
- FROG
- GOAT
- PANDA
- CAMEL
- STRIDER
- TURTLE
- CAT
- SKELETON_HORSE
- COW
- LLAMA
- ARMADILLO
- HOGLIN
- POLAR_BEAR
- WOLF
- MULE
- OCELOT
- FOX
- DOLPHIN
- COD
- PUFFERFISH
- TADPOLE
- SQUID
- SALMON
- TROPICAL_FISH
- GLOW_SQUID

View File

@ -1,35 +1,63 @@
########################################################################################### ---
# 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: block-limits:
hit-limit: "&c[material] omezen na [number]!" hit-limit: "&c[material] omezen na [number]!"
entity-limits: entity-limits:
hit-limit: "&cSpawnování [entity] omezeno na [number]!" hit-limit: "&cSpawnování [entity] omezeno na [number]!"
limits: limits:
panel-title: "Omezení ostrovů" panel-title: Omezení ostrovů
admin: admin:
limits: limits:
main: main:
parameters: "<player>" parameters: "<player>"
description: "ukázat omezení ostrova hráče" description: ukázat omezení ostrova hráče
calc: calc:
parameters: "<player>" parameters: "<player>"
description: "přepočítat omezení ostrova hráče" description: přepočítat omezení ostrova hráče
finished: "&aPřepočítání ostrova úspěšně dokončeno!" finished: "&aPřepočítání ostrova úspěšně dokončeno!"
offset:
unknown: "&c Neznámý materiál nebo entita [name]."
main:
description: umožňuje spravovat limity offsetů pro materiály a entity
set:
parameters: "<hráč> <materiál|entita> <číslo>"
description: nastaví nový offset pro limit materiálu nebo entity
success: "&a Mezní posun pro [name] je nastaven na [number]."
same: "&c Mezní posun pro [name] je již [number]."
add:
parameters: "<hráč> <materiál|entita> <číslo>"
description: přidá offset pro limit materiálu nebo entity
success: "&a Posun limitu pro [name] se zvýší na [number]."
remove:
parameters: "<hráč> <materiál|entita> <číslo>"
description: snižuje offset pro limit materiálu nebo entity
success: "&a Posun limitu pro [name] se sníží na [number]."
reset:
parameters: "<hráč> <materiál|entita>"
description: odstraní offset pro materiál nebo entitu
success: "&a Mezní posun pro [jméno] je nastaven na 0."
view:
parameters: "<hráč> <materiál|entita>"
description: zobrazuje posun pro materiál nebo entitu
message: "&odsazení [name] je nastaveno na [number]."
island: island:
limits: limits:
description: "ukázat omezení tvého ostrova" description: ukázat omezení tvého ostrova
max-color: "&c" max-color: "&c"
regular-color: "&a" regular-color: "&a"
block-limit-syntax: "[number]/[limit]" block-limit-syntax: "[number]/[limit]"
no-limits: "&cTento svět nemá žádné omezení" no-limits: "&c V tomto světě nejsou stanovena žádná omezení"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Tento ostrov nemá vlastníka"
not-on-island: "&c Toto umístění nemá nastavena omezení."
recount: recount:
description: "přepočítá omezení tvého ostrova" description: přepočítá omezení tvého ostrova
now-recounting: "&b Nyní vyprávění. Může to chvíli trvat, čekejte prosím..."
in-progress: "&c Probíhá obnovení ostrova. Čekejte prosím..."
time-out: "&c Časový limit při přepočítávání. Je ostrov opravdu velký?"

View File

@ -14,12 +14,51 @@ admin:
parameters: "<player>" parameters: "<player>"
description: Berechne die Insel Limitierungen für den Spieler neu description: Berechne die Insel Limitierungen für den Spieler neu
finished: "&aInselberechnung erfolgreich abgeschlossen!" finished: "&aInselberechnung erfolgreich abgeschlossen!"
offset:
unknown: "&c Unbekanntes Material oder Entität [name]."
main:
description: ermöglicht die Verwaltung von Grenzwertverschiebungen für Materialien
und Entitäten
set:
parameters: "<Spieler> <Material|Entität> <Nummer>"
description: legt einen neuen Offset für Material- oder Entity-Grenzwert fest
success: "&a Der Grenzoffset für [name] ist auf [number] eingestellt."
same: "&c Der Grenzoffset für [name] ist bereits [number]."
add:
parameters: "<Spieler> <Material|Entität> <Nummer>"
description: fügt einen Offset für Material- oder Entitätslimit hinzu
success: "&a Der Limit-Offset für [name] wird bis [number] erhöht."
remove:
parameters: "<Spieler> <Material|Entität> <Nummer>"
description: reduziert den Offset für Material- oder Entitätslimit
success: "&a Der Grenzoffset für [name] wird auf [number] reduziert."
reset:
parameters: "<Spieler> <Material|Entität>"
description: Entfernt den Offset für Material oder Entität
success: "&a Der Grenzoffset für [name] wird auf 0 gesetzt."
view:
parameters: "<Spieler> <Material|Entität>"
description: zeigt den Offset für Material oder Entität an
message: "&a [name]-Offset ist auf [number] gesetzt."
island: island:
limits: limits:
description: Zeige deine Insel Limitierungen description: Zeige deine Insel Limitierungen
max-color: "&c" max-color: "&c"
regular-color: "&a" regular-color: "&a"
block-limit-syntax: "[number]/[limit]" block-limit-syntax: "[number]/[limit]"
no-limits: "&cDiese Welt hat keine Limitierungen" no-limits: "&c Es gibt keine Grenzen in dieser Welt"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Diese Insel hat keinen Besitzer"
not-on-island: "&c Für diesen Standort sind keine Beschränkungen festgelegt."
recount: recount:
description: Zählt die Limitierungen für deine Insel auf description: Zählt die Limitierungen für deine Insel auf
now-recounting: "&b Ich erzähle jetzt. Dies kann eine Weile dauern, bitte warten..."
in-progress: "&c Inselrückmeldung läuft. Warten Sie mal..."
time-out: "&c Zeitüberschreitung beim Erzählen. Ist die Insel wirklich so groß?"

View File

@ -10,7 +10,6 @@ entity-limits:
limits: limits:
panel-title: "Island limits" panel-title: "Island limits"
admin: admin:
limits: limits:
main: main:
@ -19,15 +18,51 @@ admin:
calc: calc:
parameters: "<player>" parameters: "<player>"
description: "recalculate the island limits for player" description: "recalculate the island limits for player"
finished: "&aIsland recalc finished successfully!" finished: "&a Island recalc finished successfully!"
offset:
unknown: "&c Unknown material or entity [name]."
description: "allows to manage limits offsets for materials and entities"
set:
parameters: "<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: island:
limits: limits:
description: "show your island limits" description: "show your island limits"
max-color: "&c" max-color: "&c"
regular-color: "&a" regular-color: "&a"
block-limit-syntax: "[number]/[limit]" block-limit-syntax: "[number]/[limit]"
no-limits: "&cNo limits set in this world" no-limits: "&c No limits set in this world"
panel:
title-syntax: '[title] [sort]'
entity-group-name-syntax: '[name]'
entity-name-syntax: '[name]'
block-name-syntax: '[name]'
A2Z: "a > z"
Z2A: "z > a"
errors:
no-owner: "&c That island has no owner"
not-on-island: "&c This location does not have limits set."
recount: recount:
description: "recounts limits for your island" 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,65 @@
---
block-limits:
hit-limit: "&c[material] limitado a [number]!"
entity-limits:
hit-limit: "¡Aparición de &c[entity] limitada a [number]!"
limits:
panel-title: Límites de la isla
admin:
limits:
main:
parameters: "<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: "&c No hay límites establecidos en este mundo."
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Esa isla no tiene dueño"
not-on-island: "&c Esta ubicación no tiene límites establecidos."
recount:
description: cuenta los límites para tu isla
now-recounting: "&b Ahora contando. Esto podría tomar un tiempo, por favor espere..."
in-progress: "&c El recuento de la isla está en progreso. Espere por favor..."
time-out: "&c Ha pasado demasiado tiempo contando. ¿La isla es realmente grande?"

View File

@ -0,0 +1,66 @@
---
block-limits:
hit-limit: "&c[material] limité à [number]!"
entity-limits:
hit-limit: "&c[entity] spawning limited to [number]!"
limits:
panel-title: Limites de l'île
admin:
limits:
main:
parameters: "<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!"
offset:
unknown: "&c Matériau ou entité inconnu [name]."
main:
description: permet de gérer les décalages de limites pour les matériaux et
les entités
set:
parameters: "<joueur> <matériel|entité> <numéro>"
description: définit un nouveau décalage pour la limite de matériau ou d'entité
success: "&a Le décalage limite pour [name] est défini sur [number]."
same: "&c Le décalage limite pour [name] est déjà [number]."
add:
parameters: "<joueur> <matériel|entité> <numéro>"
description: ajoute un décalage pour la limite de matériau ou d'entité
success: "&a Le décalage limite pour [name] est augmenté jusqu'à [number]."
remove:
parameters: "<joueur> <matériel|entité> <numéro>"
description: réduit le décalage pour la limite de matériau ou d'entité
success: "&a Le décalage limite pour [name] est réduit jusqu'à [number]."
reset:
parameters: "<joueur> <matériel|entité>"
description: supprime le décalage pour le matériau ou l'entité
success: "&a Le décalage limite pour [name] est défini sur 0."
view:
parameters: "<joueur> <matériel|entité>"
description: affiche le décalage pour le matériau ou l'entité
message: "&a [name] le décalage est défini sur [number]."
island:
limits:
description: affichez les limites de votre île
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Aucune limite n'est fixée dans ce monde"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Cette île n'a pas de propriétaire"
not-on-island: "&c Cet emplacement n'a pas de limites définies."
recount:
description: recompte les limites de votre île
now-recounting: "&b Recomptage en cours. Cela peut prendre un certain temps,
veuillez patienter..."
in-progress: "&c Le recomptage de l'île est en cours. Veuillez patienter s'il
vous plaît..."
time-out: "&c Time out lors du recomptage. L'île est-elle vraiment grande?"

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material] ograničen na [number]!"
entity-limits:
hit-limit: "&c[entity] stvaranje ograničeno na [number]!"
limits:
panel-title: Granice otoka
admin:
limits:
main:
parameters: "<igrač>"
description: pokazati ograničenja otoka za igrača
calc:
parameters: "<igrač>"
description: ponovno izračunati ograničenja otoka za igrača
finished: "&a Recalc otoka uspješno završen!"
offset:
unknown: "&c Nepoznati materijal ili entitet [name]."
main:
description: omogućuje upravljanje pomacima ograničenja za materijale i entitete
set:
parameters: "<igrač> <materijal|entitet> <broj>"
description: postavlja novi pomak za ograničenje materijala ili entiteta
success: "&a Ograničenje pomaka za [name] postavljeno je na [number]."
same: "&c Ograničenje pomaka za [name] već je [number]."
add:
parameters: "<igrač> <materijal|entitet> <broj>"
description: dodaje pomak za ograničenje materijala ili entiteta
success: "&a Ograničenje pomaka za [name] povećava se do [number]."
remove:
parameters: "<igrač> <materijal|entitet> <broj>"
description: smanjuje pomak za ograničenje materijala ili entiteta
success: "&a Ograničenje pomaka za [name] smanjeno je do [number]."
reset:
parameters: "<igrač> <materijal|entitet>"
description: uklanja pomak za materijal ili entitet
success: "&a Ograničenje pomaka za [name] postavljeno je na 0."
view:
parameters: "<igrač> <materijal|entitet>"
description: prikazuje pomak za materijal ili entitet
message: "&pomak [name] postavljen je na [number]."
island:
limits:
description: pokazati granice vašeg otoka
max-color: i c
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c U ovom svijetu nema ograničenja"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Taj otok nema vlasnika"
not-on-island: "&c Ova lokacija nema postavljena ograničenja."
recount:
description: preračunava ograničenja za vaš otok
now-recounting: "&b Sada prepričavam. Ovo bi moglo potrajati, pričekajte..."
in-progress: "&c Otok je u tijeku. Molimo pričekajte..."
time-out: "&c Time out kod prepričavanja. Je li otok stvarno velik?"

View File

@ -1,33 +1,63 @@
########################################################################################### ---
# 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: block-limits:
hit-limit: "&c[material] limitálva ennyire [number]!" hit-limit: "&c[material] limitálva ennyire [number]!"
entity-limits: entity-limits:
hit-limit: "&c[entity] idézés limitálva ennyire [number]!" hit-limit: "&c[entity] idézés limitálva ennyire [number]!"
limits: limits:
panel-title: "Sziget limitek" panel-title: Sziget limitek
admin: admin:
limits: limits:
main: main:
parameters: "<player>" parameters: "<player>"
description: "Egy játékos sziget limitjének megtekintése" description: Egy játékos sziget limitjének megtekintése
calc: calc:
parameters: "<player>" parameters: "<player>"
description: "Egy játékos sziget limitjének újraszámolása" description: Egy játékos sziget limitjének újraszámolása
finished: "&aA Sziget újraszámolás sikeresen befejeződött!" finished: "&aA Sziget újraszámolás sikeresen befejeződött!"
offset:
unknown: "&c Ismeretlen anyag vagy entitás [name]."
main:
description: lehetővé teszi az anyagok és entitások limiteltolásainak kezelését
set:
parameters: "<lejátszó> <anyag|entitás> <szám>"
description: új eltolást állít be az anyag- vagy entitáskorláthoz
success: "&a A [name] határeltolódása [number] értékre van állítva."
same: "&c A [name] korláteltolása már [number]."
add:
parameters: "<lejátszó> <anyag|entitás> <szám>"
description: eltolást ad hozzá az anyag- vagy entitáskorláthoz
success: "&a A [name] határeltolódása a [number] értékig nő."
remove:
parameters: "<lejátszó> <anyag|entitás> <szám>"
description: csökkenti az anyag- vagy entitáskorlát ellentételezését
success: "&a A [name] limiteltolása [number] értékre csökken."
reset:
parameters: "<lejátszó> <anyag|entitás>"
description: eltávolítja az anyag vagy entitás eltolását
success: "&a A [name] határeltolódása 0-ra van állítva."
view:
parameters: "<lejátszó> <anyag|entitás>"
description: anyag vagy entitás eltolását jeleníti meg
message: "&a [name] eltolás értéke [number]."
island: island:
limits: limits:
description: "Sziget limitek megtekintése" description: Sziget limitek megtekintése
max-color: "&c" max-color: "&c"
regular-color: "&a" regular-color: "&a"
block-limit-syntax: "[number]/[limit]" block-limit-syntax: "[number]/[limit]"
no-limits: "&cNincsenek limitek ebben a világban" no-limits: "&c Nincsenek korlátok ezen a világon"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Annak a szigetnek nincs gazdája"
not-on-island: "&c Ennek a helynek nincs korlátja beállítva."
recount: recount:
description: "Limitek újraszámolása a szigeteden" description: Limitek újraszámolása a szigeteden
now-recounting: "&b Most mesélünk. Ez eltarthat egy ideig, kérem, várjon..."
in-progress: "&c Sziget visszaállítása folyamatban van. Kérem, várjon..."
time-out: "&c Időtúllépés az újraszámláláskor. Tényleg nagy a sziget?"

View File

@ -0,0 +1,65 @@
---
block-limits:
hit-limit: "&c[material] dibatasi sampai [number]!"
entity-limits:
hit-limit: "&c[entity] spawning dibatasi sampai [number]!"
limits:
panel-title: Batasan Pulau
admin:
limits:
main:
parameters: "<pemain>"
description: menampilkan batasan pulau untuk pemain
calc:
parameters: "<pemain>"
description: menghitung ulang batasan pulau untuk pemain
finished: "&aPenghitungan ulang pulau selesai tanpa masalah!"
offset:
unknown: "&c Material atau entitas tidak dikenal [name]."
main:
description: memungkinkan untuk mengelola batas offset untuk material dan
entitas
set:
parameters: "<pemain> <material|entitas> <angka>"
description: menetapkan offset baru untuk batasan material atau entitas
success: "&a Batas offset untuk [name] ditetapkan ke [number]."
same: "&a Batas offset untuk [name] ditetapkan ke [number]."
add:
parameters: "<pemain> <material|entitas> <angka>"
description: menambahkan offset untuk batasan material atau entitas
success: "&a Batas offset untuk [name] ditingkatkan hingga [number]."
remove:
parameters: "<pemain> <material|entitas> <angka>"
description: mengurangi offset untuk batas material atau entitas
success: "&a Batas offset untuk [name] dikurangi hingga [number]."
reset:
parameters: "<pemain> <material|entitas>"
description: menghapus offset untuk material atau entitas
success: "&a Batas offset untuk [name] ditetapkan ke 0."
view:
parameters: "<pemain> <material|entitas>"
description: menampilkan offset untuk material atau entitas
message: "&a [name] offset diatur ke [number]."
island:
limits:
description: menampilkan batasan pulau kamu
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Tidak ada batasan yang ditetapkan di dunia ini"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Pulau itu tidak memiliki pemilik"
not-on-island: "&c Lokasi ini tidak memiliki batasan yang ditetapkan."
recount:
description: menghitung ulang batasan untuk pulau kamu
now-recounting: "&b Menghitung ulang. Membutuhkan waktu beberapa saat, silahkan
tunggu..."
in-progress: "&c Perhitungan pulau sedang diproses. Silahkan tunggu..."
time-out: "&c Gagal menghitung ulang. Apakah pulau terlalu besar?"

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material] limitato a [number]!"
entity-limits:
hit-limit: "&c[entity] la generazione è limitata a [number]!"
limits:
panel-title: Limiti dell'isola
admin:
limits:
main:
parameters: "<giocatore>"
description: mostra i limiti dell'isola per il giocatore
calc:
parameters: "<giocatore>"
description: ricalcola i limiti dell'isola per il giocatore
finished: "&a Il ricalcolo dell'isola è stato completato con successo!"
offset:
unknown: "&c Materiale o entità sconosciuta [name]."
main:
description: consente di gestire gli offset dei limiti per materiali ed entità
set:
parameters: "<giocatore> <materiale|entità> <numero>"
description: imposta un nuovo offset per il limite di materiale o entità
success: "&a Il limite di offset per [name] è impostato su [number]."
same: "&c Il limite di offset per [name] è già [number]."
add:
parameters: "<giocatore> <materiale|entità> <numero>"
description: aggiunge offset per limite di materiale o entità
success: "&a Il limite di offset per [name] è aumentato fino a [number]."
remove:
parameters: "<giocatore> <materiale|entità> <numero>"
description: riduce l'offset per il limite di materiale o entità
success: "&a Il limite di offset per [name] è ridotto fino a [number]."
reset:
parameters: "<giocatore> <materiale|entità>"
description: rimuove l'offset per materiale o entità
success: "&a Il limite di offset per [name] è impostato su 0."
view:
parameters: "<giocatore> <materiale|entità>"
description: visualizza l'offset per materiale o entità
message: "&a [name] offset è impostato su [number]."
island:
limits:
description: mostra i limiti della tua isola
max-color: "&C"
regular-color: "&UN"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Non ci sono limiti in questo mondo"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Quell'isola non ha proprietario"
not-on-island: "&c Questa posizione non ha limiti impostati."
recount:
description: racconta i limiti per la tua isola
now-recounting: "&b Ora sto raccontando. Potrebbe volerci un po', attendi..."
in-progress: "&c Il conteggio dell'isola è in corso. Attendi..."
time-out: "&c Time out quando si racconta. L'isola è davvero grande?"

View File

@ -1,23 +1,62 @@
---
block-limits: block-limits:
hit-limit: "c[material]は[number]に制限されています!" hit-limit: '&c[material]は[number]に制限されています!'
entity-limits: entity-limits:
hit-limit: "c[entity]の生成は[number]に制限されています!" hit-limit: '&c[entity]の生成は[number]に制限されています!'
limits:
panel-title: 島の制限
admin: admin:
limits: limits:
main: main:
parameters: "<プレイヤー>" parameters: <プレイヤー>
description: プレイヤーの島の制限を表示する description: プレイヤーの島の制限を表示する
calc: calc:
parameters: "<プレイヤー>" parameters: <プレイヤー>
finished: "a島の再計算が正常に完了しました"
description: プレイヤーの島の制限を再計算します description: プレイヤーの島の制限を再計算します
finished: '&a 島の再計算が正常に完了しました!'
offset:
unknown: '&c 不明な素材または実体 [name]。'
main:
description: 材料とエンティティの制限オフセットを管理できます
set:
parameters: <プレイヤー> <マテリアル|エンティティ> <番号>
description: 材料またはエンティティの制限の新しいオフセットを設定します
success: '&a [name] の制限オフセットが [number] に設定されています。'
same: '&c [name] の制限オフセットはすでに [number] です。'
add:
parameters: <プレイヤー> <マテリアル|エンティティ> <番号>
description: 材料またはエンティティの制限のオフセットを追加します
success: '&a [name] の制限オフセットが [number] まで増加されます。'
remove:
parameters: <プレイヤー> <マテリアル|エンティティ> <番号>
description: 材料またはエンティティの制限のオフセットを削減します
success: '&a [name] の制限オフセットが [number] まで削減されます。'
reset:
parameters: <プレイヤー> <マテリアル|エンティティ>
description: マテリアルまたはエンティティのオフセットを削除します
success: '&a [name] の制限オフセットが 0 に設定されています。'
view:
parameters: <プレイヤー> <マテリアル|エンティティ>
description: 材料またはエンティティのオフセットを表示します
message: '&a [name] オフセットが [number] に設定されています。'
island: island:
limits: limits:
block-limit-syntax: "[number]/[limit]"
description: 島の限界を示す description: 島の限界を示す
max-color: "c" block-limit-syntax: '[number]/[limit]'
regular-color: "a" no-limits: この世に限界はない
no-limits: "cこの世界には制限がありません" panel:
limits: title-syntax: '[title] [sort]'
panel-title: 島の制限 entity-group-name-syntax: '[name]'
entity-name-syntax: '[name]'
block-name-syntax: '[name]'
A2Z: a > z
Z2A: z > a
errors:
no-owner: その島には所有者がいない
not-on-island: '&c この場所には制限が設定されていません。'
recount:
description: あなたの島の限界を語る
now-recounting: '&b 今、話を戻します。これにはしばらく時間がかかるかもしれませんので、お待ちください...'
in-progress: '&c 島の回復作業が進行中です。お待ちください...'
time-out: '&c もう一度話すとタイムアウトになります。島は本当に大きいですか?'
max-color: '&c'
regular-color: '&a'

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material]는 [number]개로 제한됩니다!"
entity-limits:
hit-limit: "&c[entity]는 [number]개로 제한됩니다!"
limits:
panel-title: 섬의 경계
admin:
limits:
main:
parameters: "<플레이어>"
description: 플레이어에게 섬의 한계를 보여주세요
calc:
parameters: "<플레이어>"
description: 플레이어의 섬 한계를 다시 계산합니다
finished: "&a 섬 재계산이 성공적으로 완료되었습니다!"
offset:
unknown: "&c 알 수 없는 자료 또는 엔터티 [name]."
main:
description: 재료 및 엔티티에 대한 제한 오프셋을 관리할 수 있습니다.
set:
parameters: "<플레이어> <소재|엔티티> <숫자>"
description: 재료 또는 엔티티 제한에 대한 새로운 오프셋을 설정합니다.
success: "&a [name]에 대한 제한 오프셋이 [number]로 설정되었습니다."
same: "&c [name]에 대한 제한 오프셋은 이미 [number]입니다."
add:
parameters: "<플레이어> <소재|엔티티> <숫자>"
description: 재료 또는 엔티티 제한에 대한 오프셋을 추가합니다.
success: "&a [name]에 대한 오프셋 제한이 [number]까지 증가합니다."
remove:
parameters: "<플레이어> <소재|엔티티> <숫자>"
description: 재료 또는 엔티티 제한에 대한 오프셋을 줄입니다.
success: "&a [name]에 대한 오프셋 제한은 [number]까지 감소합니다."
reset:
parameters: "<플레이어> <소재|엔티티>"
description: 재료 또는 엔티티에 대한 오프셋을 제거합니다.
success: "&a [name]에 대한 제한 오프셋이 0으로 설정되었습니다."
view:
parameters: "<플레이어> <소재|엔티티>"
description: 재료 또는 엔티티에 대한 오프셋을 표시합니다.
message: "&a [name] 오프셋이 [number]로 설정되었습니다."
island:
limits:
description: 섬의 경계를 보여주세요
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c 이 세상에는 제한이 없습니다"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: 가 > 지
Z2A: 지 > 아
errors:
no-owner: "&c 그 섬에는 주인이 없다"
not-on-island: "&c 이 위치에는 제한이 설정되어 있지 않습니다."
recount:
description: 당신의 섬에 대한 한계를 다시 계산합니다
now-recounting: "&b 지금 다시 계산 중입니다. 시간이 좀 걸릴 수 있으니 기다려 주세요..."
in-progress: "&c 섬 복구가 진행 중입니다. 잠시만 기다려 주세요..."
time-out: "&c 계산할 때 시간 초과. 섬이 정말 큰가요?"

View File

@ -1,25 +1,63 @@
--- ---
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: block-limits:
hit-limit: "&c[material] ierobežots līdz [number]!" hit-limit: "&c[material] ierobežots līdz [number]!"
entity-limits: entity-limits:
hit-limit: "&c[entity] rašanās ierobežots līdz [number]!" 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: limits:
panel-title: Salas ierobežojumi panel-title: Salas ierobežojumi
admin:
limits:
main:
parameters: "<spēlētājs>"
description: rāda salas ierobežojumus priekš spēlētāja
calc:
parameters: "<spēlētājs>"
description: pārrēķinā salas ierobežojumus priekš spēlētāja
finished: "&aSalas ierobežojumu pārrēķināšana pabeigta!"
offset:
unknown: un c Nezināms materiāls vai vienība [name].
main:
description: ļauj pārvaldīt limitu kompensācijas materiāliem un entītijām
set:
parameters: "<spēlētājs> <materiāls|vienība> <skaitlis>"
description: nosaka jaunu nobīdi materiāla vai entītijas ierobežojumam
success: "&a Ierobežojuma nobīde [name] ir iestatīta uz [number]."
same: "&c [name] limita nobīde jau ir [number]."
add:
parameters: "<spēlētājs> <materiāls|vienība> <skaitlis>"
description: pievieno nobīdi materiāla vai entītijas ierobežojumam
success: "&a Ierobežojuma nobīde [name] tiek palielināta līdz [number]."
remove:
parameters: "<spēlētājs> <materiāls|vienība> <skaitlis>"
description: samazina materiāla vai entītijas limita kompensāciju
success: "&a Ierobežojuma nobīde [name] tiek samazināta līdz [number]."
reset:
parameters: "<spēlētājs> <materiāls|vienība>"
description: noņem materiāla vai entītijas nobīdi
success: "&a Ierobežojuma nobīde [name] ir iestatīta uz 0."
view:
parameters: "<spēlētājs> <materiāls|vienība>"
description: parāda nobīdi materiālam vai vienībai
message: "&a [name] nobīde ir iestatīta uz [number]."
island:
limits:
description: rāda tavas salas ierobežojumus
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Šajā pasaulē nav noteikti ierobežojumi"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Tai salai nav saimnieka"
not-on-island: "&c Šai vietai nav noteikti ierobežojumi."
recount:
description: pārrēķina ierobežojumus tavai salai
now-recounting: "&b Tagad atstāsta. Tas var aizņemt kādu laiku, lūdzu, uzgaidiet..."
in-progress: "&c Notiek salas atjaunošana. Lūdzu, uzgaidiet..."
time-out: "&c Noildze, pārskaitot. Vai tiešām sala ir liela?"

View File

@ -0,0 +1,65 @@
---
block-limits:
hit-limit: "&c[material] beperkt tot [number]!"
entity-limits:
hit-limit: "&c[entity] spawning beperkt tot [number]!"
limits:
panel-title: Eilandgrenzen
admin:
limits:
main:
parameters: "<speler>"
description: toon de eilandgrenzen voor de speler
calc:
parameters: "<speler>"
description: herbereken de eilandlimieten voor de speler
finished: "&a Eiland herberekening succesvol afgerond!"
offset:
unknown: "&c Onbekend materiaal of entiteit [name]."
main:
description: maakt het mogelijk om limietoffsets voor materialen en entiteiten
te beheren
set:
parameters: "<speler> <materiaal|entiteit> <nummer>"
description: stelt nieuwe offset in voor materiaal- of entiteitslimiet
success: "&a Limietoffset voor [name] is ingesteld op [number]."
same: "&c Limietoffset voor [name] is al [number]."
add:
parameters: "<speler> <materiaal|entiteit> <nummer>"
description: voegt offset toe voor materiaal- of entiteitslimiet
success: "&a Limietoffset voor [name] is verhoogd tot [number]."
remove:
parameters: "<speler> <materiaal|entiteit> <nummer>"
description: vermindert offset voor materiaal- of entiteitslimiet
success: "&a Limietoffset voor [name] is verlaagd tot [number]."
reset:
parameters: "<speler> <materiaal|entiteit>"
description: verwijdert offset voor materiaal of entiteit
success: "&a Limietoffset voor [name] is ingesteld op 0."
view:
parameters: "<speler> <materiaal|entiteit>"
description: geeft offset weer voor materiaal of entiteit
message: "&a [name] offset is ingesteld op [number]."
island:
limits:
description: toon uw eilandgrenzen
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Er zijn geen grenzen gesteld in deze wereld"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Dat eiland heeft geen eigenaar"
not-on-island: "&c Voor deze locatie zijn geen limieten ingesteld."
recount:
description: vertelt over de grenzen van uw eiland
now-recounting: "&b Nu aan het navertellen. Dit kan even duren, even geduld
aub..."
in-progress: "&c Eilandrecovery is bezig. Even geduld..."
time-out: "&c Time-out bij het navertellen. Is het eiland echt groot?"

View File

@ -1,27 +1,64 @@
# ---
# 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: block-limits:
hit-limit: '&c[material] limitowany do [number]!' hit-limit: "&c[material] limitowany do [number]!"
entity-limits: entity-limits:
hit-limit: '&cSpawnowanie [entity] limitowane do [number]!' hit-limit: "&cSpawnowanie [entity] limitowane do [number]!"
limits: limits:
panel-title: Limity wysp panel-title: Limity wysp
admin: admin:
limits: limits:
main: main:
parameters: <gracz> parameters: "<gracz>"
description: pokazuje limity wysp gracza description: pokazuje limity wysp gracza
calc: calc:
parameters: <gracz> parameters: "<gracz>"
description: ponownie oblicza limity wyspy dla gracza description: ponownie oblicza limity wyspy dla gracza
finished: '&aPrzeliczanie zakończone!' 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: island:
limits: limits:
description: pokazuje limity twojej wyspy description: pokazuje limity twojej wyspy
max-color: '&c' max-color: "&c"
regular-color: '&a' regular-color: "&a"
block-limit-syntax: '[number]/[limit]' block-limit-syntax: "[number]/[limit]"
no-limits: '&cBrak ustawionych limitów.' no-limits: "&c Na tym świecie nie ma żadnych ograniczeń"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Ta wyspa nie ma właściciela"
not-on-island: "&c Ta lokalizacja nie ma ustalonych ograniczeń."
recount: recount:
description: określa limity dla twojej wyspy 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,63 @@
---
block-limits:
hit-limit: "&c[material] limitado a [number]!"
entity-limits:
hit-limit: "&c[entity] gerando limitado a [number]!"
limits:
panel-title: Limites da ilha
admin:
limits:
main:
parameters: "<jogador>"
description: mostrar os limites da ilha para o jogador
calc:
parameters: "<jogador>"
description: recalcular os limites da ilha para o jogador
finished: "&a O recálculo da ilha foi concluído com sucesso!"
offset:
unknown: "&c Material ou entidade desconhecida [name]."
main:
description: permite gerenciar limites de deslocamento para materiais e entidades
set:
parameters: "<jogador> <material|entidade> <número>"
description: define novo deslocamento para limite de material ou entidade
success: "&a O deslocamento de limite para [name] é definido como [number]."
same: "&c O deslocamento limite para [name] já é [number]."
add:
parameters: "<jogador> <material|entidade> <número>"
description: adiciona deslocamento para limite de material ou entidade
success: "&a O deslocamento de limite para [name] é aumentado até [number]."
remove:
parameters: "<jogador> <material|entidade> <número>"
description: reduz o deslocamento para o limite de material ou entidade
success: "&a O deslocamento de limite para [name] é reduzido até [number]."
reset:
parameters: "<jogador> <material|entidade>"
description: remove deslocamento para material ou entidade
success: "&a O deslocamento de limite para [name] é definido como 0."
view:
parameters: "<jogador> <material|entidade>"
description: exibe deslocamento para material ou entidade
message: "&a [name] deslocamento é definido como [number]."
island:
limits:
description: mostre os limites da sua ilha
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Não há limites neste mundo"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Aquela ilha não tem dono"
not-on-island: "&c Este local não possui limites definidos."
recount:
description: reconta limites para sua ilha
now-recounting: "&b Agora recontando. Isso pode demorar um pouco, aguarde..."
in-progress: "&c A recontagem da ilha está em andamento. Aguarde..."
time-out: "&c Tempo limite ao recontar. A ilha é realmente grande?"

View File

@ -0,0 +1,65 @@
---
block-limits:
hit-limit: "&c[material] limitat la [number]!"
entity-limits:
hit-limit: "&c[entity] reproducere limitata la [number]!"
limits:
panel-title: Limitele insulei
admin:
limits:
main:
parameters: "<jucator>"
description: Arata limitele insulei pentru jucator
calc:
parameters: "<jucator>"
description: recalculeaza limitele insulei pentru jocator
finished: "&aRecalcularea insulei terminata cu succes!"
offset:
unknown: "&c Material sau entitate necunoscută [name]."
main:
description: permite gestionarea decalajelor de limite pentru materiale și
entități
set:
parameters: "<player> <material|entity> <number>"
description: stabilește o nouă compensare pentru limita de material sau entitate
success: "&a Decalajul limită pentru [name] este setat la [number]."
same: "&c Decalajul limită pentru [name] este deja [number]."
add:
parameters: "<player> <material|entity> <number>"
description: adaugă compensare pentru limita de material sau entitate
success: "&a Compensarea limită pentru [name] este mărită până la [number]."
remove:
parameters: "<player> <material|entity> <number>"
description: reduce compensarea pentru limita de material sau entitate
success: "&a Limita decalajului pentru [name] este redusă până la [number]."
reset:
parameters: "<player> <material|entity>"
description: elimină decalajul pentru material sau entitate
success: "&a Decalajul limită pentru [name] este setat la 0."
view:
parameters: "<player> <material|entity>"
description: afișează offset pentru material sau entitate
message: "&a [name] offset este setat la [number]."
island:
limits:
description: iti arata limitele insulei
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Nu există limite stabilite în această lume"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Insula aceea nu are proprietar"
not-on-island: "&c Această locație nu are limite setate."
recount:
description: renumara limitele insulei tale
now-recounting: "&b Acum povestind. Acest lucru ar putea dura ceva timp, vă
rugăm să așteptați..."
in-progress: "&c Recuperarea insulei este în curs. Va rugam asteptati..."
time-out: "&c Timpul expirat când relatați. Este insula cu adevărat mare?"

View File

@ -1,32 +1,65 @@
########################################################################################### ---
# 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 #
###########################################################################################
entity-limits:
hit-limit: "&3&LS&b&lC &8&L» &e[entity] &4varlığından &5[number] &4tane koyabilirsin!"
block-limits: block-limits:
hit-limit: "&3&LS&b&lC &8&L» &e[material] &4eşyasından &5[number] &4koyabilirsin!" hit-limit: "&e[material] &4eşyasından &5[number] &4koyabilirsin!"
entity-limits:
hit-limit: "&e[entity] &4varlığından &5[number] &4tane koyabilirsin!"
limits: limits:
panel-title: "&3&lSon&b&lCesurlar &eAda limiti" panel-title: "&eAda limiti"
admin: admin:
limits: limits:
main: main:
parameters: "<player>" parameters: "<player>"
description: "Oyuncu için ada limitlerini göster." description: Oyuncu için ada limitlerini göster.
calc: calc:
parameters: "<player>" parameters: "<player>"
description: "Oyuncu için ada limitlerini tekrar hesapla." description: Oyuncu için ada limitlerini tekrar hesapla.
finished: "&aIsland recalc finished sucessfully!" finished: "&aAda hesaplaması başarıyla yapıldı."
offset:
unknown: "&c Bilinmeyen malzeme veya varlık [name]."
main:
description: malzemeler ve varlıklar için limit ofsetlerini yönetmeye olanak
tanır
set:
parameters: "<oyuncu> <malzeme|varlık> <sayı>"
description: malzeme veya varlık sınırı için yeni ofset ayarlar
success: "&[name] için Limit ofseti [number] olarak ayarlandı."
same: "&c [name] için sınır ofseti zaten [number]'dır."
add:
parameters: "<oyuncu> <malzeme|varlık> <sayı>"
description: malzeme veya varlık sınırı için ofset ekler
success: "&[name] için Limit ofseti [number]'ya kadar artırıldı."
remove:
parameters: "<oyuncu> <malzeme|varlık> <sayı>"
description: malzeme veya varlık sınırı için ofseti azaltır
success: "&[name] için limit ofseti [number]'ya kadar azaltıldı."
reset:
parameters: "<oyuncu> <malzeme|varlık>"
description: malzeme veya varlık için ofseti kaldırır
success: "&[name] için Limit ofseti 0 olarak ayarlandı."
view:
parameters: "<oyuncu> <malzeme|varlık>"
description: malzeme veya varlık için ofseti görüntüler
message: "&a [name] ofseti [number] olarak ayarlandı."
island: island:
limits: limits:
parameters: "" description: Ada limitlerini gör.
description: "Ada limitlerini gör."
max-color: "&c" max-color: "&c"
regular-color: "&9" regular-color: "&9"
block-limit-syntax: "&5[number]&7/&e[limit]" block-limit-syntax: "&5[number]&7/&e[limit]"
no-limits: "&3&LS&c&lC &A&L» &4Bu dünyada limitler ayarlı değil!" no-limits: "&c Bu dünyada hiçbir sınır belirlenmedi"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c O adanın bir sahibi yok"
not-on-island: "&c Bu lokasyonun belirlenmiş bir sınırı yok."
recount:
description: Ada limitlerini tekrardan hesaplar.
now-recounting: "&b Şimdi anlatmaya başlıyorum. Biraz zaman alabilir, lütfen
bekleyin..."
in-progress: "&c Ada geri kazanımı devam ediyor. Lütfen bekleyin..."
time-out: "&c Anlatırken zaman aşımı. Ada gerçekten büyük mü?"

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material] обмежено [number]!"
entity-limits:
hit-limit: "&c[entity] породження обмежено до [number]!"
limits:
panel-title: Межі острова
admin:
limits:
main:
parameters: "<гравець>"
description: показати обмеження острова для гравця
calc:
parameters: "<гравець>"
description: перерахувати обмеження острова для гравця
finished: Перерахунок острова успішно завершено!
offset:
unknown: "&c Невідомий матеріал або сутність [name]."
main:
description: дозволяє керувати зсувами лімітів для матеріалів і сутностей
set:
parameters: "<гравець> <матеріал|сутність> <номер>"
description: встановлює нове зміщення для обмеження матеріалу або сутності
success: "&a Лімітне зміщення для [name] встановлено на [number]."
same: "&c Обмежене зміщення для [name] вже [number]."
add:
parameters: "<гравець> <матеріал|сутність> <номер>"
description: додає зсув для обмеження матеріалу або сутності
success: "&a Лімітне зміщення для [name] збільшується до [number]."
remove:
parameters: "<гравець> <матеріал|сутність> <номер>"
description: зменшує зсув для ліміту матеріалу або сутності
success: "&a Лімітне зміщення для [name] зменшується до [number]."
reset:
parameters: "<гравець> <матеріал|сутність>"
description: видаляє зсув для матеріалу або сутності
success: "&a Лімітне зміщення для [name] встановлено на 0."
view:
parameters: "<гравець> <матеріал|сутність>"
description: відображає зсув для матеріалу або сутності
message: "&зміщення [name] встановлено на [number]."
island:
limits:
description: покажіть межі свого острова
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c У цьому світі немає обмежень"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Цей острів не має власника"
not-on-island: "&c Це місце не має обмежень."
recount:
description: перераховує ліміти для вашого острова
now-recounting: "&b Тепер перераховую. Це може зайняти деякий час, зачекайте..."
in-progress: "&c Перерахування острова триває. Будь ласка, зачекайте..."
time-out: "&c Тайм-аут під час перерахунку. Чи справді острів великий?"

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material] bị giới hạn trong [number] khối!"
entity-limits:
hit-limit: "&c[entity] bị giới hạn trong [number] thực thể!"
limits:
panel-title: Giới hạn đảo
admin:
limits:
main:
parameters: "<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!"
offset:
unknown: "&c Tài liệu hoặc thực thể không xác định [name]."
main:
description: cho phép quản lý các giới hạn bù trừ cho vật liệu và thực thể
set:
parameters: "<người chơi> <vật liệu|thực thể> <số>"
description: thiết lập độ lệch mới cho giới hạn vật liệu hoặc thực thể
success: "&a Độ lệch giới hạn cho [name] được đặt thành [number]."
same: "&c Độ lệch giới hạn cho [name] đã là [number]."
add:
parameters: "<người chơi> <vật liệu|thực thể> <số>"
description: thêm độ lệch cho giới hạn vật liệu hoặc thực thể
success: "&a Độ lệch giới hạn cho [name] được tăng lên cho đến [number]."
remove:
parameters: "<người chơi> <vật liệu|thực thể> <số>"
description: giảm độ lệch cho giới hạn vật liệu hoặc thực thể
success: "&a Độ lệch giới hạn cho [name] được giảm xuống đến [number]."
reset:
parameters: "<người chơi> <vật liệu|thực thể>"
description: xóa phần bù cho vật liệu hoặc thực thể
success: "&a Độ lệch giới hạn cho [name] được đặt thành 0."
view:
parameters: "<người chơi> <vật liệu|thực thể>"
description: hiển thị bù trừ cho vật liệu hoặc thực thể
message: "&a [name] bù trừ được đặt thành [number]."
island:
limits:
description: xem giới hạn đảo của bạn
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c Không có giới hạn nào được đặt ra trên thế giới này"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c Hòn đảo đó không có chủ sở hữu"
not-on-island: "&c Vị trí này không có giới hạn nào được đặt ra."
recount:
description: tính toán lại giới hạn đảo của bạn
now-recounting: "&b Đang đếm lại. Việc này có thể mất một lúc, vui lòng đợi..."
in-progress: "&c Đảo đang được thu hồi. Vui lòng đợi..."
time-out: "&c Hết giờ khi kể lại. Hòn đảo có thực sự lớn không?"

View File

@ -1,32 +1,63 @@
########################################################################################### ---
# 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: block-limits:
hit-limit: "&c[material] 已限制到 [number]!" hit-limit: "&c[material] 已限制到 [number]!"
entity-limits: entity-limits:
hit-limit: "&c[entity] 生成已限制到 [number]!" hit-limit: "&c[entity] 生成已限制到 [number]!"
limits: limits:
panel-title: "岛屿限制" panel-title: 岛屿限制
admin: admin:
limits: limits:
main: main:
parameters: "<player>" parameters: "<玩家>"
description: "显示玩家的限制" description: 显示玩家的限制
calc: calc:
parameters: "<player>" parameters: "<玩家>"
description: "重新计算玩家的岛屿限制" description: 重新计算玩家的岛屿限制
finished: "&a岛屿重计算已完成!" finished: "&a岛屿重计算已完成!"
offset:
unknown: "&c 未知物质或实体 [name]。"
main:
description: 允许管理材料和实体的限制偏移
set:
parameters: "<玩家> <材质|实体> <数字>"
description: 为材料或实体限制设置新的偏移
success: "&a 将 [name] 的限制偏移量设置为 [number]。"
same: "&c [name] 的限制偏移量已经为 [number]。"
add:
parameters: "<玩家> <材质|实体> <数字>"
description: 添加材料或实体限制的偏移
success: "&a [name] 的限制偏移量增加至 [number]。"
remove:
parameters: "<玩家> <材质|实体> <数字>"
description: 减少材料或实体限制的偏移量
success: "&a [name] 的限制偏移量减少至 [number]。"
reset:
parameters: "<玩家> <材质|实体>"
description: 删除材料或实体的偏移
success: "&a [name] 的限制偏移量设置为 0。"
view:
parameters: "<玩家> <材质|实体>"
description: 显示材料或实体的偏移
message: "&a [name] offset 设置为 [number]。"
island: island:
limits: limits:
description: "显示您的岛屿限制" description: 显示您的岛屿限制
max-color: "&c" max-color: "&c"
regular-color: "&a" regular-color: "&a"
block-limit-syntax: "[number]/[limit]" block-limit-syntax: "[number]/[limit]"
no-limits: "&c此世界中无限制" no-limits: "&c 这世上没有限制"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c 那个岛没有主人"
not-on-island: "&c 此位置未设置限制。"
recount: recount:
description: "重新计数岛屿限制" description: 重新计数岛屿限制
now-recounting: "&b 开始重新计算. 可能需要一定的时间, 请稍等......"
in-progress: "&c 重新计算岛屿限制中. 请稍等......"
time-out: "&c 重新计算超时. 岛屿太大了吗?"

View File

@ -0,0 +1,63 @@
---
block-limits:
hit-limit: "&c[material]僅限[number]"
entity-limits:
hit-limit: "&c[entity] 產生僅限於 [number]"
limits:
panel-title: 島嶼限制
admin:
limits:
main:
parameters: "<玩家>"
description: 顯示玩家的島嶼限制
calc:
parameters: "<玩家>"
description: 重新計算玩家的島嶼限制
finished: "&a 島嶼重新計算成功完成!"
offset:
unknown: "&c 未知材料或實體[name]。"
main:
description: 允許管理材料和實體的限制偏移
set:
parameters: "<玩家> <材質|實體> <數字>"
description: 設定材料或實體限制的新偏移
success: "&a [name] 的限制偏移量設定為 [number]。"
same: "&c [name] 的限制偏移量已經是 [number]。"
add:
parameters: "<玩家> <材質|實體> <數字>"
description: 添加材料或實體限制的偏移量
success: "&a [name] 的限制偏移量增加到 [number]。"
remove:
parameters: "<玩家> <材質|實體> <數字>"
description: 減少材料或實體限制的偏移
success: "&a [name] 的限制偏移量減少到 [number]。"
reset:
parameters: "<玩家> <材質|實體>"
description: 刪除材質或實體的偏移
success: "&a [名稱] 的限制偏移量設定為 0。"
view:
parameters: "<玩家> <材質|實體>"
description: 顯示材料或實體的偏移
message: "&a [name] 偏移量設定為 [number]。"
island:
limits:
description: 顯示您的島嶼限制
max-color: "&c"
regular-color: "&a"
block-limit-syntax: "[number]/[limit]"
no-limits: "&c 這個世界沒有限制"
panel:
title-syntax: "[title] [sort]"
entity-group-name-syntax: "[name]"
entity-name-syntax: "[name]"
block-name-syntax: "[name]"
A2Z: a > z
Z2A: z > a
errors:
no-owner: "&c 那島沒有主人"
not-on-island: "&c 該位置沒有設定限制。"
recount:
description: 詳細說明您的島嶼的限制
now-recounting: "&b 現在重述。這可能需要一段時間,請稍候..."
in-progress: "&c 島回收正在進行中。請稍等..."
time-out: "&c 重新計數時逾時。島真的很大嗎?"

View File

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

View File

@ -1,4 +1,4 @@
package bentobox.addon.limits.listeners; package world.bentobox.limits;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@ -9,8 +9,10 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -22,6 +24,9 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.PluginManager;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -34,12 +39,12 @@ import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.addons.AddonDescription;
import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent; import world.bentobox.bentobox.api.events.team.TeamSetownerEvent;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.limits.Limits;
import world.bentobox.limits.listeners.BlockLimitsListener; import world.bentobox.limits.listeners.BlockLimitsListener;
import world.bentobox.limits.listeners.JoinListener; import world.bentobox.limits.listeners.JoinListener;
import world.bentobox.limits.mocks.ServerMocks;
import world.bentobox.limits.objects.IslandBlockCount; import world.bentobox.limits.objects.IslandBlockCount;
/** /**
@ -47,12 +52,14 @@ import world.bentobox.limits.objects.IslandBlockCount;
* *
*/ */
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@PrepareForTest( {Bukkit.class} ) @PrepareForTest({ Bukkit.class })
public class JoinListenerTest { public class JoinListenerTest {
@Mock @Mock
private Limits addon; private Limits addon;
@Mock @Mock
private Settings settings;
@Mock
private GameModeAddon bskyblock; private GameModeAddon bskyblock;
@Mock @Mock
private Player player; private Player player;
@ -68,22 +75,31 @@ public class JoinListenerTest {
private OfflinePlayer owner; private OfflinePlayer owner;
@Mock @Mock
private Island island; private Island island;
@Mock
private PluginManager pim;
private @Nullable UUID uuid = UUID.randomUUID();
@Before @Before
public void setUp() { public void setUp() {
ServerMocks.newServer();
jl = new JoinListener(addon); jl = new JoinListener(addon);
// Setup addon // Setup addon
when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock)); when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock));
when(addon.getGameModeName(any())).thenReturn("bskyblock"); when(addon.getGameModeName(any())).thenReturn("bskyblock");
when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock."); when(addon.getGameModePermPrefix(any())).thenReturn("bskyblock.");
when(addon.getSettings()).thenReturn(settings);
// Settings
when(settings.getGroupLimitDefinitions())
.thenReturn(new ArrayList<>(List.of(new EntityGroup("friendly", new HashSet<>(), -1, null))));
// Island Manager // Island Manager
when(im.hasIsland(any(), any(UUID.class))).thenReturn(true);
when(island.getUniqueId()).thenReturn("unique_id"); when(island.getUniqueId()).thenReturn("unique_id");
when(island.getOwner()).thenReturn(uuid);
when(im.getIsland(any(), any(UUID.class))).thenReturn(island); 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 // Default is that player has island
when(addon.getIslands()).thenReturn(im); when(addon.getIslands()).thenReturn(im);
// Player // Player
when(player.getUniqueId()).thenReturn(UUID.randomUUID()); when(player.getUniqueId()).thenReturn(uuid);
when(player.getName()).thenReturn("tastybento"); when(player.getName()).thenReturn("tastybento");
// No permissions by default // No permissions by default
when(player.getEffectivePermissions()).thenReturn(Collections.emptySet()); when(player.getEffectivePermissions()).thenReturn(Collections.emptySet());
@ -102,13 +118,18 @@ public class JoinListenerTest {
when(owner.isOnline()).thenReturn(true); when(owner.isOnline()).thenReturn(true);
when(owner.getPlayer()).thenReturn(player); when(owner.getPlayer()).thenReturn(player);
when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(owner); when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(owner);
when(Bukkit.getPluginManager()).thenReturn(pim);
// Island }
when(island.getOwner()).thenReturn(UUID.randomUUID());
@After
public void tearDown() {
ServerMocks.unsetBukkitServer();
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnNewIslandWrongReason() { public void testOnNewIslandWrongReason() {
@ -118,7 +139,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnNewIslandRegistered() { public void testOnNewIslandRegistered() {
@ -128,7 +150,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnNewIslandResetted() { public void testOnNewIslandResetted() {
@ -179,7 +202,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onOwnerChange(world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onOwnerChange(world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent)}.
*/ */
@Test @Test
public void testOnOwnerChange() { public void testOnOwnerChange() {
@ -192,7 +216,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoin() { public void testOnPlayerJoin() {
@ -203,7 +228,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinIBCNull() { public void testOnPlayerJoinIBCNull() {
@ -214,9 +240,9 @@ public class JoinListenerTest {
verify(bll, never()).setIsland("unique_id", ibc); verify(bll, never()).setIsland("unique_id", ibc);
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermNotLimits() { public void testOnPlayerJoinWithPermNotLimits() {
@ -232,7 +258,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsWrongSize() { public void testOnPlayerJoinWithPermLimitsWrongSize() {
@ -244,11 +271,13 @@ public class JoinListenerTest {
when(player.getEffectivePermissions()).thenReturn(perms); when(player.getEffectivePermissions()).thenReturn(perms);
PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome");
jl.onPlayerJoin(e); 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' or 'bskyblock.island.limit.ENTITY-TYPE.NUMBER' Ignoring..."); 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 method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsInvalidMaterial() { public void testOnPlayerJoinWithPermLimitsInvalidMaterial() {
@ -260,11 +289,13 @@ public class JoinListenerTest {
when(player.getEffectivePermissions()).thenReturn(perms); when(player.getEffectivePermissions()).thenReturn(perms);
PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome");
jl.onPlayerJoin(e); 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. Ignoring..."); 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 method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsWildcard() { public void testOnPlayerJoinWithPermLimitsWildcard() {
@ -276,11 +307,13 @@ public class JoinListenerTest {
when(player.getEffectivePermissions()).thenReturn(perms); when(player.getEffectivePermissions()).thenReturn(perms);
PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome");
jl.onPlayerJoin(e); jl.onPlayerJoin(e);
verify(addon).logError("Player tastybento has permission: 'bskyblock.island.limit.*' but wildcards are not allowed. Ignoring..."); 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 method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsNotNumber() { public void testOnPlayerJoinWithPermLimitsNotNumber() {
@ -292,11 +325,13 @@ public class JoinListenerTest {
when(player.getEffectivePermissions()).thenReturn(perms); when(player.getEffectivePermissions()).thenReturn(perms);
PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome"); PlayerJoinEvent e = new PlayerJoinEvent(player, "welcome");
jl.onPlayerJoin(e); jl.onPlayerJoin(e);
verify(addon).logError("Player tastybento has permission: 'bskyblock.island.limit.STONE.abc' but the last part MUST be a number! Ignoring..."); 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 method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsSuccess() { public void testOnPlayerJoinWithPermLimitsSuccess() {
@ -313,7 +348,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}.
*/ */
@Test @Test
public void testOnPlayerJoinWithPermLimitsSuccessEntity() { public void testOnPlayerJoinWithPermLimitsSuccessEntity() {
@ -330,7 +366,26 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent)}. * 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 @Test
public void testOnPlayerJoinWithPermLimitsMultiPerms() { public void testOnPlayerJoinWithPermLimitsMultiPerms() {
@ -340,7 +395,7 @@ public class JoinListenerTest {
when(permAtt.getValue()).thenReturn(true); when(permAtt.getValue()).thenReturn(true);
perms.add(permAtt); perms.add(permAtt);
PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class); PermissionAttachmentInfo permAtt2 = mock(PermissionAttachmentInfo.class);
when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.grass.14"); when(permAtt2.getPermission()).thenReturn("bskyblock.island.limit.short_grass.14");
when(permAtt2.getValue()).thenReturn(true); when(permAtt2.getValue()).thenReturn(true);
perms.add(permAtt2); perms.add(permAtt2);
PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class); PermissionAttachmentInfo permAtt3 = mock(PermissionAttachmentInfo.class);
@ -365,7 +420,7 @@ public class JoinListenerTest {
jl.onPlayerJoin(e); jl.onPlayerJoin(e);
verify(addon, never()).logError(anyString()); verify(addon, never()).logError(anyString());
verify(ibc).setBlockLimit(eq(Material.STONE), eq(24)); verify(ibc).setBlockLimit(eq(Material.STONE), eq(24));
verify(ibc).setBlockLimit(eq(Material.GRASS), eq(14)); verify(ibc).setBlockLimit(eq(Material.SHORT_GRASS), eq(14));
verify(ibc).setBlockLimit(eq(Material.DIRT), eq(34)); verify(ibc).setBlockLimit(eq(Material.DIRT), eq(34));
verify(ibc).setEntityLimit(eq(EntityType.CHICKEN), eq(34)); verify(ibc).setEntityLimit(eq(EntityType.CHICKEN), eq(34));
verify(ibc).setEntityLimit(eq(EntityType.CAVE_SPIDER), eq(4)); verify(ibc).setEntityLimit(eq(EntityType.CAVE_SPIDER), eq(4));
@ -432,7 +487,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnUnregisterIslandNotUnregistered() { public void testOnUnregisterIslandNotUnregistered() {
@ -442,7 +498,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnUnregisterIslandNotInWorld() { public void testOnUnregisterIslandNotInWorld() {
@ -453,7 +510,8 @@ public class JoinListenerTest {
} }
/** /**
* Test method for {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}. * Test method for
* {@link world.bentobox.limits.listeners.JoinListener#onUnregisterIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/ */
@Test @Test
public void testOnUnregisterIslandInWorld() { public void testOnUnregisterIslandInWorld() {
@ -487,6 +545,4 @@ public class JoinListenerTest {
} }
} }

View File

@ -0,0 +1,366 @@
package world.bentobox.limits;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Comparator;
import java.util.Optional;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.UnsafeValues;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.addons.AddonDescription;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.limits.mocks.ServerMocks;
/**
* @author tastybento
*
*/
@SuppressWarnings("deprecation")
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class, User.class})
public class LimitsTest {
private static File jFile;
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private Island island;
@Mock
private BentoBox plugin;
@Mock
private FlagsManager fm;
@Mock
private GameModeAddon gameMode;
@Mock
private AddonsManager am;
@Mock
private BukkitScheduler scheduler;
@Mock
private Settings pluginSettings;
@Mock
private PlaceholdersManager phm;
@Mock
private CompositeCommand cmd;
@Mock
private CompositeCommand adminCmd;
@Mock
private World world;
private UUID uuid;
@Mock
private PluginManager pim;
private Limits addon;
@BeforeClass
public static void beforeClass() throws Exception {
cleanUp();
// Make the addon jar
jFile = new File("addon.jar");
// Copy over config file from src folder
Path fromPath = Paths.get("src/main/resources/config.yml");
Path path = Paths.get("config.yml");
Files.copy(fromPath, path);
try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) {
//Added the new files to the jar.
try (FileInputStream fis = new FileInputStream(path.toFile())) {
byte[] buffer = new byte[1024];
int bytesRead = 0;
JarEntry entry = new JarEntry(path.toString());
tempJarOutputStream.putNextEntry(entry);
while((bytesRead = fis.read(buffer)) != -1) {
tempJarOutputStream.write(buffer, 0, bytesRead);
}
}
}
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
Server server = ServerMocks.newServer();
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
//when(plugin.isEnabled()).thenReturn(true);
// Command manager
CommandsManager cm = mock(CommandsManager.class);
when(plugin.getCommandsManager()).thenReturn(cm);
// Player
Player p = mock(Player.class);
// Sometimes use Mockito.withSettings().verboseLogging()
when(user.isOp()).thenReturn(false);
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
when(user.getPlayer()).thenReturn(p);
when(user.getName()).thenReturn("tastybento");
User.setPlugin(plugin);
// Island World Manager
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
// Player has island to begin with
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
when(plugin.getIslands()).thenReturn(im);
// Locales
// Return the reference (USE THIS IN THE FUTURE)
when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
// Server
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
// Addon
addon = new Limits();
File dataFolder = new File("addons/Level");
addon.setDataFolder(dataFolder);
addon.setFile(jFile);
AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build();
addon.setDescription(desc);
// Addons manager
when(plugin.getAddonsManager()).thenReturn(am);
// One game mode
when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode));
AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build();
when(gameMode.getDescription()).thenReturn(desc2);
when(gameMode.getOverWorld()).thenReturn(world);
// Player command
@NonNull
Optional<CompositeCommand> opCmd = Optional.of(cmd);
when(gameMode.getPlayerCommand()).thenReturn(opCmd);
// Admin command
Optional<CompositeCommand> opAdminCmd = Optional.of(adminCmd);
when(gameMode.getAdminCommand()).thenReturn(opAdminCmd);
// Perm prefix
when(gameMode.getPermissionPrefix()).thenReturn("bskyblock.");
// Flags manager
when(plugin.getFlagsManager()).thenReturn(fm);
when(fm.getFlags()).thenReturn(Collections.emptyList());
// Bukkit
when(Bukkit.getScheduler()).thenReturn(scheduler);
ItemMeta meta = mock(ItemMeta.class);
ItemFactory itemFactory = mock(ItemFactory.class);
when(itemFactory.getItemMeta(any())).thenReturn(meta);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
UnsafeValues unsafe = mock(UnsafeValues.class);
when(unsafe.getDataVersion()).thenReturn(777);
when(Bukkit.getUnsafe()).thenReturn(unsafe);
when(Bukkit.getPluginManager()).thenReturn(pim);
// placeholders
when(plugin.getPlaceholdersManager()).thenReturn(phm);
// World
when(world.getName()).thenReturn("bskyblock-world");
// Island
when(island.getWorld()).thenReturn(world);
when(island.getOwner()).thenReturn(uuid);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
ServerMocks.unsetBukkitServer();
User.clearUsers();
Mockito.framework().clearInlineMocks();
deleteAll(new File("database"));
}
@AfterClass
public static void cleanUp() throws Exception {
new File("addon.jar").delete();
new File("config.yml").delete();
deleteAll(new File("addons"));
}
private static void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
}
/**
* Test method for {@link world.bentobox.limits.Limits#onEnable()}.
*/
@Test
public void testOnEnable() {
addon.onEnable();
File f = new File("config.yml");
assertTrue(f.exists());
}
/**
* Test method for {@link world.bentobox.limits.Limits#onDisable()}.
*/
@Test
public void testOnDisable() {
addon.onDisable();
}
/**
* Test method for {@link world.bentobox.limits.Limits#getSettings()}.
*/
@Test
public void testGetSettings() {
assertNull(addon.getSettings());
addon.onEnable();
world.bentobox.limits.Settings set = addon.getSettings();
assertFalse(set.getLimits().isEmpty());
}
/**
* Test method for {@link world.bentobox.limits.Limits#getGameModes()}.
*/
@Test
public void testGetGameModes() {
assertTrue(addon.getGameModes().isEmpty());
addon.onEnable();
assertFalse(addon.getGameModes().isEmpty());
}
/**
* Test method for {@link world.bentobox.limits.Limits#getBlockLimitListener()}.
*/
@Test
public void testGetBlockLimitListener() {
assertNull(addon.getBlockLimitListener());
addon.onEnable();
assertNotNull(addon.getBlockLimitListener());
}
/**
* Test method for {@link world.bentobox.limits.Limits#inGameModeWorld(org.bukkit.World)}.
*/
@Test
public void testInGameModeWorld() {
addon.onEnable();
assertFalse(addon.inGameModeWorld(world));
when(gameMode.inWorld(world)).thenReturn(true);
assertTrue(addon.inGameModeWorld(world));
}
/**
* Test method for {@link world.bentobox.limits.Limits#getGameModeName(org.bukkit.World)}.
*/
@Test
public void testGetGameModeName() {
when(gameMode.inWorld(world)).thenReturn(true);
assertTrue(addon.getGameModeName(world).isEmpty());
addon.onEnable();
assertEquals("BSkyBlock", addon.getGameModeName(world));
}
/**
* Test method for {@link world.bentobox.limits.Limits#getGameModePermPrefix(org.bukkit.World)}.
*/
@Test
public void testGetGameModePermPrefix() {
when(gameMode.inWorld(world)).thenReturn(true);
addon.onEnable();
assertEquals("bskyblock.", addon.getGameModePermPrefix(world));
}
/**
* Test method for {@link world.bentobox.limits.Limits#isCoveredGameMode(java.lang.String)}.
*/
@Test
public void testIsCoveredGameMode() {
assertFalse(addon.isCoveredGameMode("BSkyBlock"));
addon.onEnable();
assertTrue(addon.isCoveredGameMode("BSkyBlock"));
}
/**
* Test method for {@link world.bentobox.limits.Limits#getJoinListener()}.
*/
@Test
public void testGetJoinListener() {
assertNull(addon.getJoinListener());
addon.onEnable();
assertNotNull(addon.getJoinListener());
}
}

View File

@ -1,4 +1,4 @@
package world.bentobox.limits.commands; package world.bentobox.limits.commands.player;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -27,15 +27,17 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.limits.Limits; import world.bentobox.limits.Limits;
import world.bentobox.limits.Settings;
import world.bentobox.limits.objects.IslandBlockCount;
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@PrepareForTest( Bukkit.class ) @PrepareForTest( Bukkit.class )
public class LimitPanelTest { public class LimitTabTest {
@Mock @Mock
private Limits addon; private Limits addon;
private LimitPanel lp; private LimitTab lp;
@Mock @Mock
private Island island; private Island island;
@ -47,15 +49,19 @@ public class LimitPanelTest {
private World end; private World end;
@Mock @Mock
private BentoBox plugin; private BentoBox plugin;
@Mock @Mock
private IslandWorldManager iwm; private IslandWorldManager iwm;
@Mock
private Settings settings;
@Before @Before
public void setUp() throws Exception { public void setUp() {
// Island // Island
when(island.getWorld()).thenReturn(world); when(island.getWorld()).thenReturn(world);
// Addon // Addon
when(addon.getPlugin()).thenReturn(plugin); when(addon.getPlugin()).thenReturn(plugin);
when(addon.getSettings()).thenReturn(settings);
when(settings.getLimits()).thenReturn(Collections.emptyMap());
when(plugin.getIWM()).thenReturn(iwm); when(plugin.getIWM()).thenReturn(iwm);
when(iwm.isNetherIslands(any())).thenReturn(true); when(iwm.isNetherIslands(any())).thenReturn(true);
when(iwm.isEndIslands(any())).thenReturn(true); when(iwm.isEndIslands(any())).thenReturn(true);
@ -68,11 +74,11 @@ public class LimitPanelTest {
when(world.getEntities()).thenReturn(Collections.singletonList(entity)); when(world.getEntities()).thenReturn(Collections.singletonList(entity));
when(nether.getEntities()).thenReturn(Collections.singletonList(entity)); when(nether.getEntities()).thenReturn(Collections.singletonList(entity));
when(end.getEntities()).thenReturn(Collections.singletonList(entity)); when(end.getEntities()).thenReturn(Collections.singletonList(entity));
lp = new LimitPanel(addon); lp = new LimitTab(addon, new IslandBlockCount("", ""), Collections.emptyMap(), island, world, null, LimitTab.SORT_BY.A2Z);
} }
@After @After
public void tearDown() throws Exception { public void tearDown() {
} }
@Test @Test

View File

@ -0,0 +1,146 @@
package world.bentobox.limits.listeners;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.limits.Limits;
import world.bentobox.limits.Settings;
import world.bentobox.limits.listeners.EntityLimitListener.AtLimitResult;
import world.bentobox.limits.objects.IslandBlockCount;
@RunWith(PowerMockRunner.class)
@PrepareForTest( Bukkit.class )
public class EntityLimitListenerTest {
@Mock
private Limits addon;
private EntityLimitListener ell;
@Mock
private Island island;
@Mock
private LivingEntity ent;
@Mock
private BlockLimitsListener bll;
@Mock
private World world;
private List<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());
}
}

View File

@ -0,0 +1,118 @@
package world.bentobox.limits.mocks;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Server;
import org.bukkit.Tag;
import org.bukkit.UnsafeValues;
import org.eclipse.jdt.annotation.NonNull;
public final class ServerMocks {
public static @NonNull Server newServer() {
Server mock = mock(Server.class);
Logger noOp = mock(Logger.class);
when(mock.getLogger()).thenReturn(noOp);
when(mock.isPrimaryThread()).thenReturn(true);
// Unsafe
UnsafeValues unsafe = mock(UnsafeValues.class);
when(mock.getUnsafe()).thenReturn(unsafe);
// Server must be available before tags can be mocked.
Bukkit.setServer(mock);
// Bukkit has a lot of static constants referencing registry values. To initialize those, the
// registries must be able to be fetched before the classes are touched.
Map<Class<? extends Keyed>, Object> registers = new HashMap<>();
doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> {
Registry<?> registry = mock(Registry.class);
Map<NamespacedKey, Keyed> cache = new HashMap<>();
doAnswer(invocationGetEntry -> {
NamespacedKey key = invocationGetEntry.getArgument(0);
// Some classes (like BlockType and ItemType) have extra generics that will be
// erased during runtime calls. To ensure accurate typing, grab the constant's field.
// This approach also allows us to return null for unsupported keys.
Class<? extends Keyed> constantClazz;
try {
//noinspection unchecked
constantClazz = (Class<? extends Keyed>) clazz
.getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType();
} catch (ClassCastException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
return null;
}
return cache.computeIfAbsent(key, key1 -> {
Keyed keyed = mock(constantClazz);
doReturn(key).when(keyed).getKey();
return keyed;
});
}).when(registry).get(notNull());
return registry;
})).when(mock).getRegistry(notNull());
// Tags are dependent on registries, but use a different method.
// This will set up blank tags for each constant; all that needs to be done to render them
// functional is to re-mock Tag#getValues.
doAnswer(invocationGetTag -> {
Tag<?> tag = mock(Tag.class);
doReturn(invocationGetTag.getArgument(1)).when(tag).getKey();
doReturn(Set.of()).when(tag).getValues();
doAnswer(invocationIsTagged -> {
Keyed keyed = invocationIsTagged.getArgument(0);
Class<?> type = invocationGetTag.getArgument(2);
if (!type.isAssignableFrom(keyed.getClass())) {
return null;
}
// Since these are mocks, the exact instance might not be equal. Consider equal keys equal.
return tag.getValues().contains(keyed)
|| tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey()));
}).when(tag).isTagged(notNull());
return tag;
}).when(mock).getTag(notNull(), notNull(), notNull());
// Once the server is all set up, touch BlockType and ItemType to initialize.
// This prevents issues when trying to access dependent methods from a Material constant.
try {
Class.forName("org.bukkit.inventory.ItemType");
Class.forName("org.bukkit.block.BlockType");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return mock;
}
public static void unsetBukkitServer() {
try {
Field server = Bukkit.class.getDeclaredField("server");
server.setAccessible(true);
server.set(null, null);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private ServerMocks() {
}
}