Compare commits

...

538 Commits

Author SHA1 Message Date
tastybento fc9b00233b
Sends messages only once to all players on the island (#2389)
Was sending to visitors and all players so visitors saw it twice.
2024-05-26 17:56:46 -07:00
tastybento 1fd4a9043f
Protect pumpkins from being sheared (#2388)
Requires Paper
2024-05-26 17:45:45 -07:00
tastybento ee3b0bfcc2
Update en-US.yml (#2387) 2024-05-26 17:36:45 -07:00
tastybento 35704b3fd3
Update zh-CN.yml (#2386) 2024-05-26 17:34:01 -07:00
tastybento 60fa60372d Fix tests 2024-05-26 17:27:41 -07:00
tastybento 8b0a5a3d0b Do not load all players just to get a name. 2024-05-26 17:01:02 -07:00
tastybento ca15740a8c
Fixes a bunch of items related to making the cache smaller (#2383)
* Fixes a bunch of items related to making the cache smaller

* Fix test
2024-05-25 22:38:53 -07:00
tastybento 5e5707f2a2
Fixes #2377, was not accounting for unknown UUIDs (#2380) 2024-05-24 16:43:55 -07:00
tastybento ef58838c41
Fixes #2378 where an unknown UUID was yielding null instead of "" (#2379) 2024-05-24 15:57:46 -07:00
BONNe 885d2449d9
Fixes creeper ignation by visitors (#2375)
This fixes a long-standing bug which was introduced with a code that prevented hostile entities from targeting visitors.  

As player was not a target for creeper it allowed it to explode.

This code change prevents visitors from igniting creepers as I do not see a reason why we should allow them to ignite them, while still protecting from griefing. 

Addresses issue reported in #2372
2024-05-24 07:22:20 -07:00
tastybento 888b485f82 Fixes #2376 2024-05-24 07:18:39 -07:00
tastybento e2d9c2ce34
Avoids loading islands into cache unless they are needed. (#2373)
* Avoids loading islands into cache unless they are needed.

* Adjust methods that were calling all islands

When we cached all island, this was an inexpensive call but not now. The
methods remain but pull from the database directly. The use of them were
changed to be player specific.
2024-05-23 21:42:14 -07:00
tastybento 8aba736383
Fixes breaking rooted dirt exploit (#2371) 2024-05-19 15:35:38 -07:00
tastybento 50276cb8e5 Abstract out getting and setting islands by ID
This is preparation for potentially making the cache smaller and pulling
from the database instead when required. However, there are issues with
this because some calls can result in loading the whole database anyway.
2024-05-18 22:49:20 -07:00
tastybento d701b7e43c
Uses Bukkit version method instead of class names (#2370)
* Uses Bukkit version method instead of class names

See https://forums.papermc.io/threads/important-dev-psa-future-removal-of-cb-package-relocation.1106/

* Fix tests

* Fix server compatibility reporting issue with Paper

* Remove unused import
2024-05-18 21:47:09 -07:00
tastybento 2fc3396a8f Remove debug code that slipped in. 2024-05-18 20:08:21 -07:00
tastybento 99717f5b60
Reduces storage of Island objects in the cache #2360 (#2369) 2024-05-18 19:48:42 -07:00
tastybento 290158e6ef
Allow NPC's to hit players (#2368)
* WIP for debug only

* Allow attacks from NPC's
2024-05-18 11:07:59 -07:00
tastybento 7126e837ed
Admin command updates (#2367)
* Enables tp'ing to specific islands of a player

* Admin delete command. Fixes to admin tp command.
2024-05-16 21:02:45 -07:00
tastybento 45e5621d4c
Update README.md 2024-05-16 17:22:07 -07:00
tastybento f536a13c59 Back to 1.20.6 2024-05-12 17:20:14 -07:00
tastybento f4780659e3 Downgrade to 1.20.5 to get tests to pass.
Tech debt - need to reword tests for 1.20.6
2024-05-11 13:21:00 -07:00
tastybento aad50eab38 Just use null. 2024-05-11 13:03:50 -07:00
tastybento 24d81da907 Update to latest 1.20.6 API for PlayerDeathEvent 2024-05-11 12:55:14 -07:00
tastybento d288528a17
2356 better deletion (#2364)
* Fix 1.20.4 backwards compatibility

* Improve deletion speed and memory usage
2024-05-11 11:07:47 -07:00
tastybento 83698c267f
Purges based on team members all being offline for too long (#2362)
* Purges based on team members all being offline for too long

* Fix to riff off real team members not trusts and banned as well
2024-05-11 08:50:28 -07:00
tastybento 52a280dc0b
Remove an unused Map in cache. (#2361) 2024-05-11 08:50:03 -07:00
tastybento 4a0d44c035
Implement new API for ItemsAdder item deletion (#2353) 2024-05-11 08:49:47 -07:00
tastybento b8e1f33800
Fixes #2352 obsidian scooping NPE (#2358) 2024-05-07 21:25:58 -07:00
tastybento d8891796cd JavaDoc fixes and fix for Particle enums 2024-05-05 21:28:04 -07:00
tastybento 61e7c22bbc
Add a hook for Multipaper (#2354) 2024-05-05 21:11:16 -07:00
tastybento b1fe76c45d
Multipaper (#2343)
* Switch to use database for team invites.

* WIP multipaper

* Fixes teams. Test still need to be fixed.

* Islands are now updated correctly across servers.

This build has a lot of debug in it!

* Fix tests

* Remove debug

* Remove primary island listing

* Version id

* Fix team management and ranks

* Removed debug

* Handle island deletion better

* Island deletion across servers.

* Fix bug with MythicMobs changes #2340

* 2.4.0

* Load of debug - trying to solve the settings slowness

* Debug debug

* Bug found - addPlayer being called instead of getPlayer

* Uncomment code after debug

* Fix tests
2024-05-04 22:27:58 -07:00
tastybento 5afd454fb3 Update Slimefun 2024-05-04 21:19:21 -07:00
tastybento a55c51412d Shift Github Build Script to Java 21 2024-05-04 21:05:19 -07:00
tastybento 01a8055379
Merge pull request #2348 from BentoBoxWorld/1.20.5_compatibility
1.20.6 compatibility
2024-05-04 13:29:15 -07:00
tastybento 744665a16e
Merge branch 'develop' into 1.20.5_compatibility 2024-05-01 18:01:51 -07:00
tastybento 3e4ff33d30
Merge pull request #2349 from BentoBoxWorld/Blueprint_Bundle_Info
Write the Blueprint bundle meta data to admin info
2024-05-01 17:58:34 -07:00
tastybento 09ede87971 Write the Blueprint bundle meta data to admin info 2024-05-01 17:58:05 -07:00
tastybento b1418c144f
Update README.md 2024-05-01 08:05:11 -07:00
tastybento 15335eb992 Add NMS for latest 2024-04-29 22:26:09 -07:00
tastybento e33823d0c0 Fix tests 2024-04-29 21:37:08 -07:00
tastybento 63cc0a01d9 Fix compatibility for 1.20.6 2024-04-29 19:46:06 -07:00
tastybento 6949432cb6 1.20.5 compatibility. Not all backward compatibility done
Won't run on 1.20.4 yet without stopping due to enum incompatibility
2024-04-28 17:56:02 -07:00
tastybento 69a22e917e Fix MythicMobs test 2024-04-14 22:11:41 -07:00
tastybento ffb955b22b Fix bug with MythicMobs changes #2340 2024-04-14 22:00:37 -07:00
tastybento 3de0ff236e Fix tests 2024-04-08 23:01:45 -07:00
tastybento c86eb6a19c Make sure it's the user's island that the target is being kicked from 2024-04-08 22:11:52 -07:00
tastybento e7055c6cba Remove player from island, not all islands when kicked. 2024-04-08 21:53:18 -07:00
tastybento 5834dcbb59 Fix placeholders manager test 2024-04-08 20:15:34 -07:00
tastybento 2c75939bc3 JavaDoc and import changes 2024-04-06 09:08:11 -07:00
tastybento e570401912
Merge pull request #2337 from BentoBoxWorld/2329_placeholders_for_islands
Added placeholders for island names and member lists #2329
2024-04-04 16:30:20 -07:00
tastybento f6f4da1c89 Added placeholders for island names and member lists #2329 2024-04-04 12:59:20 -07:00
tastybento 6106b661e9
Merge pull request #2335 from BentoBoxWorld/2328_team_members_can_have_islands
Remove restrictions on having multiple islands for team members.
2024-04-04 10:23:12 -07:00
tastybento e1536fcae0 Remove superfluous null check 2024-04-04 09:12:45 -07:00
tastybento 1c19703f44 Merge if statement 2024-04-04 09:11:49 -07:00
tastybento 24b7d26fbe NPE avoid. 2024-04-04 09:09:46 -07:00
tastybento ec60991aeb Merge branch 'develop' into 2328_team_members_can_have_islands 2024-04-01 10:59:30 -07:00
tastybento 33000f9371
Merge pull request #2336 from BentoBoxWorld/2333_bundle_limits
Add feature to limit blueprint availability.
2024-03-31 18:17:59 -07:00
tastybento 83eaa50b49 Refactor to improve code quality 2024-03-31 18:16:45 -07:00
tastybento 1215a43766 Add feature to limit blueprint availability. 2024-03-31 17:29:18 -07:00
tastybento 81f765df36 Fix invites and accepts. 2024-03-30 20:57:20 -07:00
tastybento 0e6a25d74b WIP - needs work on team invites. 2024-03-29 20:26:07 -07:00
tastybento 2b19d43c85 Remove restrictions on having multiple islands for team members.
Added API to enable checking for teams on islands easier.
2024-03-29 19:38:44 -07:00
tastybento 1bce4ec1b9 Limit blueprint pasting to world y coords. Addresses #2334 2024-03-28 21:58:01 -07:00
tastybento ea8562f351 Remove more debug 2024-03-28 21:42:33 -07:00
tastybento d8f2c12fe5 Remove pasting blueprints as stone debug. 2024-03-28 21:27:48 -07:00
tastybento b734d579a1
Merge pull request #2332 from BentoBoxWorld/admin_range_multi_island
Adds support for multi islands to the admin range command
2024-03-24 18:47:39 -07:00
tastybento 44454f5854 Adds support for multi islands to the admin range command 2024-03-24 18:44:24 -07:00
tastybento da590ce319
Merge pull request #2331 from BentoBoxWorld/sort_islands
Sort player's islands by age so they are always in the same order.
2024-03-21 19:30:42 -07:00
tastybento 6599e3de80 Sort player's islands by age so they are always in the same order. 2024-03-21 19:20:31 -07:00
tastybento 3c6e3d1286
Merge pull request #2327 from BentoBoxWorld/2320_hide_flags_in_other_world
Fix #2320. Enables hiding of flags when in another world
2024-03-16 10:26:27 -07:00
tastybento d77c94c30c Fix #2320. Enables hiding of flags when in another world
The world was being taken from the user's location not the panel's world
2024-03-16 10:04:28 -07:00
tastybento d240e9c8d8 Version 2.2.1 2024-03-16 09:15:46 -07:00
tastybento 75412a4674 Fix bug 2024-03-15 19:24:41 -07:00
tastybento 40e96b9169 Remove useeless eq 2024-03-15 18:53:08 -07:00
tastybento db2b97d2fc Remove ; after record definition 2024-03-15 18:52:37 -07:00
tastybento 5ad2ba1cd9 Remove useless eq()'s 2024-03-15 18:52:24 -07:00
tastybento 6127cdced1 Reduce complexity. 2024-03-15 18:49:20 -07:00
tastybento 91998b4e24 Use method reference 2024-03-15 18:47:14 -07:00
tastybento 06ca7a311a Simplify code. Fix code smells. 2024-03-15 18:46:07 -07:00
tastybento e4e92b9634 Fix code smell 2024-03-15 18:45:55 -07:00
tastybento 0c4a4ba862 Fix tests. Added coops and trusted to team GUI view. 2024-03-15 07:50:50 -07:00
tastybento 3907cba08f
Merge branch 'master' into develop 2024-03-14 21:17:03 -07:00
tastybento 4064c9a241
Merge pull request #2324 from BentoBoxWorld/team_gui_rewrite
Team gui rewrite
2024-03-14 21:13:35 -07:00
tastybento b45c842c2f
Merge pull request #2323 from BentoBoxWorld/team_fixes
Fix promote and demote #2322
2024-03-14 21:13:21 -07:00
tastybento e2a4233f69 Fix for IslandPromoteCommandTest 2024-03-14 21:11:22 -07:00
tastybento 977c82015b Rewrite. Tested with players. 2024-03-14 20:59:20 -07:00
tastybento 6db04f872b Fix promote and demote #2322 2024-03-14 18:16:09 -07:00
tastybento 6fccf80477
Merge pull request #2321 from BentoBoxWorld/max_islands_per_island
Allow the maxhomes to apply per island.
2024-03-13 18:01:18 -07:00
tastybento eef3dcbc46 Allow the maxhomes to apply per island. 2024-03-13 17:58:28 -07:00
tastybento 253e5d7101 Use method references. 2024-03-10 22:36:42 -07:00
tastybento 1c4be17690
Merge pull request #2319 from BentoBoxWorld/better_blueprint_unzip_security
Uses path normalization to prevent directory traversal attacks.
2024-03-10 11:50:16 -07:00
tastybento dc42f51168 Uses path normalization to prevent directory traversal attacks. 2024-03-10 11:46:44 -07:00
tastybento 1fb6a8a27c
Merge pull request #2318 from BentoBoxWorld/team_owner_bug
Fixes bug where non-members could be made island owners.
2024-03-10 11:45:20 -07:00
tastybento 4170616e47 Fixes bug where non-members could be made island owners. 2024-03-10 11:29:51 -07:00
tastybento 7f532b1257 Resolve JavaDoc issues 2024-03-10 11:21:03 -07:00
tastybento c39bd75837
Merge pull request #2317 from BentoBoxWorld/mythic_mobs
Mythic mobs
2024-03-10 10:41:36 -07:00
tastybento 4810c4c4ad Adds the ability to include MythicMobs in Blueprints. Fixes #2316 2024-03-10 10:40:26 -07:00
tastybento cb7c63a520 Version 2.1.0 2024-03-09 10:29:17 -08:00
tastybento dcc3992762
Merge pull request #2315 from BentoBoxWorld/2313_Crafting_protection 2024-03-04 12:04:25 -08:00
tastybento 57164dd846 Fixes #2313 2024-03-04 07:01:47 -08:00
tastybento 69017860a0
Merge pull request #2314 from BentoBoxWorld/2309_admin_setowner
2309 admin setowner
2024-03-03 16:08:17 -08:00
tastybento 24c68a0d95 Rewrite of admin setowner command #2309 2024-03-03 16:07:49 -08:00
tastybento 994019836a Merge branch 'develop' into admin_setowner 2024-03-03 14:35:18 -08:00
tastybento bbafa6d340
Merge pull request #2312 from BentoBoxWorld/2311_team_kick_gui_fix
Fix for Island team kick requires confirmation when using GUI #2311
2024-03-03 14:32:23 -08:00
tastybento 19d81c70c6 Fix for Island team kick requires confirmation when using GUI #2311 2024-03-02 19:29:26 -08:00
tastybento 7d52325196 Version 2.1.2 2024-03-02 19:23:31 -08:00
tastybento 2d08365afc
Merge pull request #2308 from BentoBoxWorld/develop
Release 2.1.1
2024-03-01 22:16:29 -08:00
tastybento a3a4a70921 Fix NPE #2310 2024-03-01 21:51:10 -08:00
tastybento bb9ed87175 WIP 2024-03-01 21:39:18 -08:00
tastybento 0e833de22a
Update pom.xml Version 2.1.1 2024-02-26 13:47:56 -08:00
tastybento 95d89c56bc
Merge pull request #2291 from BentoBoxWorld/develop
Version 2.1.0
2024-02-25 14:58:12 -08:00
tastybento 4ddb204fcf
Merge pull request #2307 from BentoBoxWorld/2305_respawn_to_home
Change respawn point to the default home on the island. #2305
2024-02-24 16:08:32 -08:00
tastybento 562b515bfe Change respawn point to the default home on the island. #2305 2024-02-24 16:06:56 -08:00
tastybento 0a9da71c65 Fix admin commands to avoid removing players from more than one island 2024-02-24 15:51:24 -08:00
tastybento 631a15563d Remove deprecated methods from IslandsManager and IslandCache 2024-02-24 15:07:05 -08:00
tastybento f93c9ba556
Merge pull request #2306 from BentoBoxWorld/admin_disband_update
Better enable disbanding of teams via admin command
2024-02-24 14:48:56 -08:00
tastybento f568ccd2d6
Merge branch 'develop' into admin_disband_update 2024-02-24 14:48:35 -08:00
tastybento f11a56cc6b Better enable disbanding of teams via admin command 2024-02-24 14:47:22 -08:00
tastybento ad009156e5
Merge pull request #2304 from BentoBoxWorld/bukkit_paste
Bukkit paste
2024-02-23 23:08:27 -08:00
tastybento 78f84de3cb Use Bukkit pasting if user is too close to new island.
This prevents players experiencing invisible blocks at the expense of
lag.
2024-02-23 23:04:47 -08:00
tastybento 070c1102db Use Bukkit for Admin BP pasting. 2024-02-23 22:05:32 -08:00
tastybento a1d19fae74 WIP - use Bukkit for bp admin pasting. 2024-02-23 21:56:32 -08:00
tastybento 59124cfa8a Fix NPE with unregister command 2024-02-23 21:51:17 -08:00
tastybento 887f09c41b
Merge pull request #2302 from BentoBoxWorld/2296_unregister_register_fix
Adds multi-island support for the Admin Register and Unregister commands.
2024-02-19 16:52:20 -08:00
tastybento e8acd05dbe Adjust null check. 2024-02-19 16:51:08 -08:00
tastybento 3784d5d28d Added tests. Added check for world. Removed y ignore for closest island. 2024-02-19 16:27:38 -08:00
tastybento 95b7ed137f Added tests 2024-02-19 15:45:47 -08:00
tastybento 87b147ace4 Allow multi-island registrations 2024-02-19 12:03:53 -08:00
tastybento 1846ed8088 Added tests 2024-02-19 11:36:47 -08:00
tastybento af843aeb35 Unregister coded. 2024-02-19 10:55:15 -08:00
tastybento b43197d54f
Merge pull request #2300 from BentoBoxWorld/2299_placeholder_counts
Placeholder counts
2024-02-18 14:44:42 -08:00
tastybento 38551a138a Fix tests 2024-02-18 14:43:29 -08:00
tastybento 83479ac8a4 Added placeholders for team online member counts #2299 2024-02-18 14:36:42 -08:00
tastybento 5e4634400a Version 2.1.0 2024-02-18 14:36:18 -08:00
tastybento f81354fa3e Fix for MySQL https://github.com/BentoBoxWorld/BentoBox/issues/2298 2024-02-13 18:55:45 -08:00
tastybento 24025fd9ae Ignore test 2024-02-12 20:02:49 -08:00
tastybento 87054687f0 Add new DamageSource parameter 'null' to test events 2024-02-12 19:56:10 -08:00
tastybento 01f51f4d29 Temporarily not do IA custom block deletion. #2297 2024-02-12 19:27:56 -08:00
tastybento 59c18e26f5 Reduce duplications 2024-02-09 22:21:22 -08:00
tastybento b63aef5589 Add support for older 1.20.x versions 2024-02-09 17:40:58 -08:00
tastybento 2d0d9ac1c2 Add defensive code to log an warning on YAML config issue
Relates to https://github.com/BentoBoxWorld/SkyGrid/issues/67
2024-02-07 19:02:45 -08:00
tastybento 8217b375d5 Fix test. 2024-02-03 12:11:57 -08:00
tastybento 43db8d346c Added ability to hide command rank commands by Ops. 2024-02-03 11:32:28 -08:00
tastybento 9acb79fc60 Added extra info to version command on version used. 2024-02-03 08:00:17 -08:00
tastybento 6316ff4a93 Add some defensive code to try and load addons if they are not pladdons 2024-02-01 22:46:00 -08:00
tastybento 471e8d681b Version 2.0.1 2024-01-30 23:16:49 -08:00
tastybento 196e90bf4e
Merge pull request #2204 from BentoBoxWorld/develop
Release 2.0.0
2024-01-30 22:38:04 -08:00
tastybento c100fe0047 Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2024-01-30 22:31:01 -08:00
tastybento b2550882ba Add optimization for Slimefun deletion. #2289 2024-01-30 22:30:51 -08:00
BONNe 6bf12d5e20
Fixes EXP reset for players onJoin and onLeave (#2286)
The total experience does not reset player collected exp. This fixes that, as it will set level to 0 and progress in towards next level to 0
2024-01-30 19:32:13 -08:00
tastybento c9b7074e79 Fix test 2024-01-27 20:31:51 -08:00
tastybento b25c65b7b2
Center default home (#2287)
* Center default home

* Remove debug
2024-01-27 17:06:31 -08:00
tastybento 787424b999 Check for SHORT_GRASS before using it. #2283 2024-01-23 12:50:42 -08:00
tastybento 13bd1bb8f3
Fix for #2281 (#2282) 2024-01-22 14:50:24 -08:00
tastybento 5d3821094c
Fix for #2279 on-island placeholder for nether and end (#2280) 2024-01-21 10:58:53 -08:00
tastybento 90a53e9fd8
Fixes #2274 hanging signs protection (#2278) 2024-01-20 08:17:50 -08:00
tastybento ffcda52912 Use latest form of icon format for TIPPED ARROWS in panels 2024-01-19 22:16:12 -08:00
tastybento b672755ef9
Use a different method to grab the DataObject classes. (#2277)
Previously, when all Addons were Addons and men were men, the
DataObjects could be tracked using the BentoBox custom ClassLoader.
However, as Addons now can be and usually are loaded using the Bukkit
Classloader, this is no longer possible. However, we can track them when
a Database class is instantiated and use that. This relies on Addons
declaring their database objects. If they have not when the migration
command is run, then obviously they cannot be transfered.
2024-01-19 21:56:19 -08:00
tastybento 7c3056b0e8 Add ability to place glowberry cave vines if planting allowed. 2024-01-15 14:48:45 -08:00
tastybento 2d44c5dbca Adds protection for berries #2272 2024-01-15 14:22:37 -08:00
tastybento ce4d8a377e JavaDoc fixes 2024-01-14 18:57:37 -08:00
tastybento cc7b1eba4c Require panel to have a world. 2024-01-13 08:01:49 -08:00
tastybento 4bfbe41956
World context settings (#2271)
* WIP to having admin setting world based on the command

and not the location of the user.

* Make world settings depend on the world of the command

Previous code based world on the location of the user when they ran the
command.

* Remove unused import

* Fix JavaDoc

* Remove unused import.
2024-01-12 20:28:07 -08:00
tastybento 76a36e685e
Enable template to determine what is shown/clickable. (#2269) 2024-01-11 19:53:27 -08:00
tastybento f1001b1d05 Do not allow invite clicks if they have been removed from the panel 2024-01-11 18:13:43 -08:00
tastybento 98d336faf0 Fix admin settings NPE. 2024-01-11 15:59:53 +09:00
tastybento edd7bcfbd2 Fix settings panel bug NPE.
The parent needs to be set before it can be used.
2024-01-11 14:38:45 +09:00
tastybento be9b429308
Faster teleports (#2267)
* Fixed bug with teleporting where home name would not be used.

* Remove debug
2024-01-10 21:09:53 -08:00
tastybento 26e5d750a0 Clarify creeper damage. Active means creepers can damage. 2024-01-09 11:57:57 +09:00
tastybento ca2976cc4b Test clean up. 2024-01-08 22:21:31 +09:00
tastybento 54a7b8b0f2 Fix to avoid admins becoming subowners when registering an island. 2024-01-08 22:21:20 +09:00
tastybento 0e0c502e1d
Team gui (#2251)
* WIP - add GUI for teams.

* WIP teams GUI

* Moe WIP on team GUI

* Modified file to avoid an NPE.

* Minor refactor to prevent int to Integer warning.

* Use latest dependencies for tests to pass.

* Move to singleton pattern for RanksManager,

* Team GUI WIP

* Added support for kick, setowner, and leave.

* Added support for accepting and rejecting an invite.

* Fixed bugs with text and operations.

* WIP for team invites.

* Adds inviting to the GUI.

* Fix tests

* Fixed minor bugs reported by SonarCloud

* Up Minecraft version and paper to 1.20.4 in POM

* Added features based on Discord feedback.

1. added some lore to the invite button to explain the players shown are
from the game world
2. removed the team state in chat
3. added a search to the invite panel

* Added back button.

Refactored some code and templates.

* Fixed bugs with teams and uncoop untrust etc of Ops.

* Return to name search GUI if name not found.

* Added return to GUI for player search

* Use Path.of
2024-01-08 20:50:26 +09:00
Rafał Chomczyk 68da898e79
Optimize CopyWorldRegenerator#copyChunkDataToChunk to reduce performance impact in case of multiple resets simultaneously (#2261) 2024-01-08 19:09:41 +09:00
tastybento c62d4f603f
Add island object to Panel for context. (#2263)
Enable Tab object to reference parent TabbedPanel in the builder. It is
late assigned after building. This enables tabs to get the parent, and
therefore get the Island object.

default methods were used to support backward compatibility.
2024-01-08 07:07:30 +02:00
tastybento 29a6a51f30 Up to 1.20.4 in POM 2024-01-05 13:22:03 +09:00
BONNe fc658ca073
Fixes warning about already existing file saving. (#2260)
Spigot JavaPlugin#saveResources either replaces or complains that replacement is disabled. So it is necessary to check if file does not exists before saving it.

Fixes #2259
2024-01-05 06:18:41 +02:00
BONNe caade1a71c
Fixes ItemParser. (#2257)
* Fixes ItemParser.

Implements new Potion and Skull parsing.
Fixes custom model data parsing.
Implements ItemFactory#createItemStack parsing that parses item using `/give` syntax.

Solves some issues mentioned in #2198

* Fixes failing BentoBoxLocaleTest

In runTime ItemFactory#createItemStack from invalid text would throw IllegalArgumentException.
2024-01-04 21:30:16 +09:00
BONNe 31fb10629a Fixes merge conflicts. 2024-01-04 10:18:25 +02:00
BONNe 38d845d2e9
Fixes issue with file exporting in Windows system. (#2256)
JAR files does not store files inside it with filesystem separator. Only spot where it makes sense to transform "/" into file separator is in output file saving.
2024-01-04 17:15:13 +09:00
BONNe bfb487342a
Customizable Island Creation Panel (#2255)
* Customizable Island Creation Panel

This implements customizable Island Creation Panel. By default, panel is generated in `/plugins/bentobox/panels` folder, however, if GameModeAddon has a specific panel defined in their folder, then that panel is taken.

* Create UnitTests for new panels

* Add island creation panel saving on loading

* Remove old IslandCreationPanel and move to the new one.

* Fixes some issues with locale and panel

---------

Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2024-01-04 17:14:23 +09:00
BONNe 3be034bfa9
Customizable Language Selection Panel (#2254)
* Customizable Language Selection Panel

This implements customizable Language Selection Panel. By default, panel is generated in `/plugins/bentobox/panels` folder, however, if GameModeAddon has a specific panel defined in their folder, then that panel is taken.

* Migrate LanguagePanelTest to customizable panel.

* Remove original LanguagePanel

* Save language_panel.yml on server starting.

* Fixes some bugs in LanguagePanel.
2024-01-04 17:09:44 +09:00
tastybento e8e1d6184e
Material Type adapter (#2253)
* Fix powermockito test dependencies.

* Add Material gson type adapter to handle old Material enums

In this case GRASS -> SHORT_GRASS
2024-01-02 12:54:50 +09:00
tastybento 0352cfdd38 Lower Spigot version back to 1.20.3 because 1.20.4 doesn't exist? 2024-01-02 09:01:45 +09:00
tastybento cc5c8aa9b6
Adds an ItemAdder hook to delete any blocks when island is deleted. (#2250)
* Adds an ItemAdder hook to delete any blocks when island is deleted.

Also includes a flag for explosions.

* Make the error reporting method non-abstract.

This is not a mandatory method for many hooks.

* Delete this class as it is not used any more and just duplicate.

* Added test class.

* Minor issues resolved.
2023-12-28 10:30:20 +09:00
tastybento 86d8d147d1
Delete slimefun chunks/blocks when island is deleted. (#2247) 2023-12-24 21:24:21 +09:00
BONNe 96499f3ad6
Improves obsidian platform generation (#2246)
The obsidian platform was not generating constantly in the same spot. It was moving depending on the entrance point (from the sides).
This code changes it, as it will move through relative blocks. 
Also, this change will sync portal and platform center points, which was not done previously. 

Fixes #2239
2023-12-24 13:19:45 +09:00
tastybento b260cf1f4f Add backward compatibility handleing for GRASS 2023-12-23 08:42:46 +09:00
tastybento f6e26aa5bd Fix admin register command to handle worlds correctly
The register command was failing if a player had more that one island,
even if the island was in a different game mode.
2023-12-19 04:51:10 -08:00
tastybento 1ef55a2b1e Fix issue where seed worlds lost generators with Multiverse on restart 2023-12-19 04:36:00 -08:00
tastybento b2f5a441e5 Added 1.20.4 compatibility. 2023-12-10 09:23:10 -08:00
tastybento 8dce036d70 Workaround to avoid massive test failures.
The addition of a null check in the Enchantment Bukkit code causes a lot
of test failures. While we work out how to mock that particular area,
this code avoids running Enchantment code when under test.
2023-12-10 09:18:54 -08:00
tastybento 55e94b4c9f Revert "Rewrote PanelItemTest to avoid null check errors in Bukkit class"
This reverts commit 5a193cda1c.
2023-12-10 09:10:48 -08:00
tastybento 5a193cda1c Rewrote PanelItemTest to avoid null check errors in Bukkit class 2023-12-09 09:25:07 -08:00
tastybento 1ab2ff278d Revert "Revert to 1.20.2 for now until 1.20.3 is more stable"
This reverts commit 1942f5f1df.
2023-12-06 19:03:41 -08:00
tastybento 1942f5f1df Revert to 1.20.2 for now until 1.20.3 is more stable 2023-12-06 17:00:38 -08:00
tastybento 79b95af8bd Try to avoid test error with Material class (which is now bigger) 2023-12-06 14:06:41 -08:00
tastybento af861f2a34 Update to 1.20.3 2023-12-06 13:45:44 -08:00
tastybento 6964f8c61a Fix MV compatibility. Fixes #2244
Provides the correct generator for the seed worlds.
2023-12-06 13:38:59 -08:00
tastybento 12bf37d2c2
No tabs (#2243)
* Remove all tabs from source and replace with 4 spaces.
2023-12-04 20:55:40 -08:00
tastybento 22f398fe53 Fix deletion of islands when chunks are not deleted. Fixes #2241 2023-12-03 19:50:51 -08:00
tastybento f256c3af8d Fixes #2240 Makes locations default to island center in IslandBaseEvent 2023-12-02 21:41:35 -08:00
tastybento 54ebf2dfd2 Fix issue where players with more than one island couldn't leave a team 2023-12-02 12:06:57 -08:00
tastybento 26e6310fba Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2023-12-02 11:51:58 -08:00
BONNe f80cf238f0
Fixes end portal finding issue when entering from side (#2238)
There was a bug in the code that prevented to finding of a portal location if the player entered from the side. 
It can happen only if the portal frame is removed, but as in vanilla it is possible, then it needs to be addressed.

The fix itself is simple: instead of checking just up from the starting location, the code will check all blocks in 10x10x10 square from the starting location and find the "closest" portal location.

This will fix #2237
2023-11-28 13:04:52 -08:00
gitlocalize-app[bot] 187ae1c61a
Add Ukrainian locale (#2235)
* Translate uk.yml via GitLocalize

* Translate uk.yml via GitLocalize

* Translate uk.yml via GitLocalize

* Update uk.yml

---------

Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: GIGABAIT <freebitcoin93@gmail.com>
Co-authored-by: BONNe <bonne@bonne.id.lv>
Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2023-11-26 10:34:27 -08:00
gitlocalize-app[bot] ea6b5dd257
Add Ukrainian locale (#2236)
* Translate uk.yml via GitLocalize

* Translate uk.yml via GitLocalize

---------

Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: BONNe <bonne@bonne.id.lv>
2023-11-26 09:59:46 -08:00
tastybento 9fe5212c3c Added AddonEvent test class. 2023-11-25 18:43:18 -08:00
tastybento 625b70de30 Simplify fillBorder 2023-11-25 18:22:37 -08:00
tastybento 4547232ac9 Better constants 2023-11-25 14:57:39 -08:00
tastybento 90751224d7 Better constants 2023-11-25 14:54:07 -08:00
tastybento 59c84990e5
Adds a method that can return a different default value than 0 (#2234) 2023-11-25 08:39:28 -08:00
Baterka 13c339ef4f
- Fixed separators to also work on Windows (#2233)
- Added missing `--add-opens=java.base/java.security=ALL-UNNAMED`
 - Added proper closing of AddonClassLoader
2023-11-25 07:45:56 -08:00
Huynh Tien 5de7302469
Switch to Spigot's PlayerProfile on HeadGetter (#2231)
* Switch to Spigot's PlayerProfile on HeadGetter

Remove AuthLib

* forgot to set meta back

* should check if the texture is not empty
2023-11-24 08:15:20 -08:00
tastybento 1cf7ccbb99 Fix bug where players couldn't invite other players.
The IslandsManager#inTeam method was returning true even if the team was
just 1 player.
2023-11-19 12:38:54 -08:00
tastybento defb1c7a6e
Better French, maybe... (#2228) 2023-11-18 18:28:34 -08:00
tastybento 0ecbd9150b Even more French fixing... 2023-11-18 15:38:06 -08:00
tastybento 2818ac8f33 Correct French 2023-11-18 15:35:39 -08:00
tastybento 70e4ae15b7 Fix gender 2023-11-18 15:34:24 -08:00
tastybento d95727fbdf Fix French - coffre, not poitrine. Chest not human chest! 2023-11-18 15:32:51 -08:00
tastybento 26dc5c5949 IslandsManager#isOwner correction and JavaDoc update 2023-11-18 13:49:33 -08:00
BONNe a35353a802
Fixes an issue with entity teleportation if nether/end worlds are disabled (#2227)
There was a bug that used old code (environment switching) for teleportation out of dimension. 
The issue should be fixed with calling just calling teleportation with portal environment.
2023-11-15 23:34:13 +02:00
tastybento 63d092db93
Multi island api update (#2224)
* Fixes bug with island team joining and deletion. Include debug.

* Remove ambiguous API with mutli-island.

* Add back deprecated methods so developers can be wartned not to use them

* Added back in deprecated methods.

* Fix bugs and NPEs.

* Fix some code smells

* Remove debug logging.
2023-11-12 12:22:12 -08:00
tastybento 19d7e2fe0a Fix island resetting. #2223
Islands were being deleted in all worlds, and all islands were being
deleted from the player instead of just the one island.
2023-11-10 17:06:46 -08:00
tastybento c63de278fe Revert "This changes the loading to not be on STARTUP (#2214)"
This reverts commit 4a6bf31dff.
2023-11-10 10:54:30 -08:00
tastybento 24148f30ba
Rank Management (#2220)
* Put ranks in the database. Prework for #1798

* Prevent coop and trust commands from being registered

if those ranks don't exist.

* Add /bbox rank command for ranks management

* Rename RanksObject to Ranks

* Strip / on commands if it exists. Fixes #2221

* Fix test failure
2023-11-10 10:53:28 -08:00
tastybento 4481881b9e Fix tests 2023-11-10 07:34:52 -08:00
BONNe 285205fe3f
Fixes a bug with EntityTeleportListener (#2222)
There was incorrect teleportation type detection, as target world were set to NORMAL. This prevented to detect that portal in opposite side exists, and should be linked to the correct position.
2023-11-10 07:23:38 -08:00
tastybento 5503ce0d90 Fixes #2221 2023-11-05 15:17:56 -08:00
tastybento 6d09a5a359 Fixes #2219. Island homes were set incorrectly with a new island. 2023-11-04 10:29:28 -07:00
BONNe 4200fe4abb
Fixes a bug that removed old owner from island (#2218)
* Fixes a bug that removed old owner from island

These changes fix a bug that was introduced by clearing the island cache from the old owner for 2.0 verison.

This will set the previous owner as sub-owner on the island.

* Fixes rank on event fired.

Changes from Visitor to Sub owner rank

* Update IslandsManager.java

Removes line that removes player from cache. 

They should remain in the cache.

---------

Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2023-10-30 09:17:26 +02:00
tastybento 4a6bf31dff
This changes the loading to not be on STARTUP (#2214)
Worlds are then created in onEnable and not one-tick later.
2023-10-28 21:16:36 -07:00
tastybento d903e57ad6
Remove the deprecated classes for 2.0 (#2216) 2023-10-28 21:16:04 -07:00
tastybento fd99da68d9 Fix double trapped (redstone) chest protection. #2215 2023-10-23 18:31:57 -07:00
gitlocalize-app[bot] 7f50073ad1
Croatian translation WIP (#2212)
* Translate hr.yml via GitLocalize
---------

Co-authored-by: tastybento <tastybento@wasteofplastic.com>
2023-10-21 16:00:25 -07:00
gitlocalize-app[bot] c4fa6cf26e
French translation WIP (#2211)
* Translate fr.yml via GitLocalize

---------

Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2023-10-21 15:33:26 -07:00
gitlocalize-app[bot] 39882cb7bc
German Translation WIP (#2210)
* Translate de.yml via GitLocalize

---------

Co-authored-by: tastybento <tastybento@wasteofplastic.com>
2023-10-21 15:01:32 -07:00
gitlocalize-app[bot] 4e0e3d2005
Portuguese translation (#2209)
* Translate pt.yml via GitLocalize

---------

Co-authored-by: dollyXtoddy <dollyxtoddy@gmail.com>
Co-authored-by: tastybento <tastybento@wasteofplastic.com>
Co-authored-by: SrVictor079 <cruz.joaovictor22@gmail.com>
2023-10-21 13:58:22 -07:00
gitlocalize-app[bot] c195baf66d
Czech translation (#2208)
* Translate cs.yml via GitLocalize

---------

Co-authored-by: tastybento <tastybento@wasteofplastic.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
2023-10-21 12:01:15 -07:00
gitlocalize-app[bot] b3808334ad
Translate ja.yml via GitLocalize (#2207)
Co-authored-by: tastybento <tastybento@wasteofplastic.com>
2023-10-21 12:00:49 -07:00
gitlocalize-app[bot] 9e8df5ce80
Chinese translation (#2206)
* Translate zh-CN.yml via GitLocalize

---------

Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: tastybento <tastybento@wasteofplastic.com>
Co-authored-by: Jeansou <bettertreebot@gmail.com>
Co-authored-by: CuteLittleSky <2173204318@qq.com>
2023-10-21 12:00:30 -07:00
gitlocalize-app[bot] 2c49e6d2f4
Hungarian translationrian (#2205)
* Translate hu.yml via GitLocalize

* Update hu.yml

Fixed placeholders.

---------

Co-authored-by: s3babyy <s3bastian540@gmail.com>
Co-authored-by: balazzrthd <balazzrt@gmail.com>
Co-authored-by: tastybento <tastybento@wasteofplastic.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: SilkyFalcon <gerolytibor2007@gmail.com>
Co-authored-by: RayenHUN <huszarikd@gmail.com>
Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2023-10-21 11:09:33 -07:00
tastybento 5ccaadb2ae Fix tests. 2023-10-21 08:19:46 -07:00
tastybento 8d0dbcdc36 Fix erroneous code that got pasted in... 2023-10-21 08:00:19 -07:00
tastybento e4836b9cef Remove island number check for owner transfer.
This is not needed anymore (we think).
2023-10-21 07:55:03 -07:00
BONNe af3b0bba2d
Fixes missing locale text
The message was linked to a non-existing path.
2023-10-20 20:01:41 +03:00
tastybento ea5529f077
Enables permissions to set how many islands a play can create. (#2201)
Multiple Island Permissions #2199
2023-10-15 19:01:02 -07:00
tastybento 489a1e332d Remove unregistration of MV worlds workaround.
This is no longer needed as with
https://github.com/Multiverse/Multiverse-Core/releases/tag/4.3.12 (or
maybe earlier) as the issue is fixed where the generator was not
remembered across reboots.
2023-10-15 13:57:48 -07:00
tastybento c8b2e1d801 Changed some tests for ItemParsing.
Potions are no longer extended or upgraded using the deprecated
PotionData. They have explict names, like "long_night_vision" or
similar. So these tests don't work any more.
2023-10-11 22:48:39 -07:00
tastybento a2f1054bbe PotionTypes have changed and now are explicitly named
For example, extended are now called
2023-10-11 22:46:51 -07:00
tastybento 9f8a6bcded Remove players from the island and cache. 2023-10-11 21:43:30 -07:00
tastybento dc51bd87b1 Fix code to have a default entry on the switch, which is now required. 2023-10-11 21:42:45 -07:00
tastybento 2b65543935 Adds InvincibleVisitorFlagDamageRemovalEvent and tests.
Resolves #2196
2023-10-08 08:56:13 -07:00
tastybento 7fbd041be9 Check passengers of jockeys when spawned. #2195 2023-10-07 11:15:23 -07:00
tastybento 6d2f79881b Fixes getOwner and getIslands to properly return islands in the world
The world was not being used for the getOwner return so if a player had
an island in any world then it was returned. This caused an NPE if the
island was then requested by getIsland because it would not be there.
2023-10-07 10:30:16 -07:00
tastybento e6ccce4a27 Added flags for Loom, stonecutter, cartography, smithing, grinding
Fixes #2194 and #2193
2023-10-07 09:52:24 -07:00
tastybento 8b78affc9b Update NMS to 1.20.2 2023-10-07 09:01:01 -07:00
tastybento 5c3ebc800c Add 1.20.2 compatibility. 2023-10-07 08:54:59 -07:00
tastybento 2bc82dd3cf
Add checking for enchantment table inventory (#2191)
Fixes #2190
2023-09-19 21:07:28 -07:00
tastybento a4bef159be
2 0 0 multi island (#2185)
* Multi world WIP - stashing

* Initial work on supporting multiple islands per player

The default allowed number is 5 for now, but will be set to 1 by
default.

Lots more work to do on this!

* More work on multi island. Fixed tests so clean compile.

* Remove unused imports

* Updated island go and homes command to multi island

Updated tests.

* Do not reload addons anymore.

* Add island name when entering or leaving own island

* Remove unused import

* Adds island names to /island go command.

* Enables more homes to be set if player has more than one island

* Switch to using a set for islands and explicit primary boolean in Island

* WIP

* Fix bugs with the go command.

* Be able to delete multiple islands, e.g. when joining a team

This is not fully tested.

* Do not remove all islands when a player does reset.

Players can reset just the island they are on.

* More fixes for go command

* Fix tests

* Fix @NonNull annotation

* Fix home syntax listing reference for IslandDeleteHome

* Fixed deletehome for multiple islands.

* Fix /island command teleport to current island default home.

* Remove deprecated code.

* Fix tag for concurrent island setting in config.yml

* Improve error when trying to make additional islands over limit

* Update config.yml

* Correctly assign invites for islands.

* Switch to canExecute API in prep for multi-island handling

* Prevent players from obtaining more concurrent islands by owner transfer

* Handle leaving and disbanding of teams

* Fix tests

* Fix minor bugs or code smells.

* Restore the quarantine code from deprecation.

This code can stay. It checks if islands can load, and if not puts them
in a trash. It does no harm.

* Remove unneeded eq()'s

* Fix tests
2023-09-16 15:55:52 -07:00
tastybento 503107a90c Fix test 2023-09-04 14:58:23 -07:00
tastybento b276c0715c Fix reference for home list syntax in IslandDeletehomeCommand 2023-09-04 12:24:56 -07:00
tastybento 8d3be9c0ff Added island member placeholders
Relates to #2180
2023-09-03 10:50:47 -07:00
tastybento ba31dd78b4 Ignoring sculk sensor test for now.
It used to work, but now doesn't. I've tried updating various plugins
and trying different things, but it seems that the mocking is not
working correctly. I don't know why.
2023-09-03 08:58:00 -07:00
tastybento a7b791f4be Fixes #2173. Copies the correct range of blocks. Added test class.
Issue was with negative numbers that (int) rounds towards zero and not
towards negative infinity.
2023-09-02 08:04:34 -07:00
gitlocalize-app[bot] bcf569c019
Indonesian translation (#2184)
* Translate id.yml via GitLocalize

* Translate id.yml via GitLocalize

* Translate id.yml via GitLocalize

---------

Co-authored-by: Dusty <siapa-yg-mau-diblokir.kfrxp@simplelogin.com>
Co-authored-by: mt-gitlocalize <mt@gitlocalize.com>
Co-authored-by: tastybento <tastybento@wasteofplastic.com>
2023-09-01 15:28:49 -07:00
tastybento b2bdce4d25
Merge pull request #2183 from BentoBoxWorld/gitlocalize-26028
Japanese update
2023-09-01 14:59:14 -07:00
tastybento a6f47e5c54 Translate ja.yml via GitLocalize 2023-09-01 21:58:31 +00:00
mt-gitlocalize 33a694cfd1 Translate ja.yml via GitLocalize 2023-09-01 21:58:30 +00:00
tastybento 6a5ea171be
Merge pull request #2182 from BentoBoxWorld/gitlocalize-26016
Few RU translations
2023-09-01 14:35:38 -07:00
tastybento d6978ce0f6 Translate ru.yml via GitLocalize 2023-09-01 21:33:37 +00:00
tastybento e23207df2f
Merge pull request #2181 from BentoBoxWorld/ru-translation
Update ru.yml
2023-09-01 14:28:46 -07:00
tastybento 0f6d5af1f8
Update ru.yml
New translation provided by @Moltanicaa https://github.com/Moltanicaa
2023-09-01 14:25:07 -07:00
BONNe da2daae990
Fixes a bug with incorrectly indexed pages +1 (#2178)
* Fixes incorrect element displays in pages 1+.

The issue was introduced by #585f720f6f4a7e9480af5065469c92e23e5a987e which reduced complexity too much.

Fixes #2177

* Reformat and reduce complexity properly.

* Rework logic for getting number of same button types in the panel

* Add more comments.

* Add proper deprecated annotation.
2023-08-29 21:19:33 +03:00
tastybento aed78038ef Fix issue with NPE when Op does Command Ranks and does not own island
Related to #2170

Added a test class for CommandRankClickListener and reworked the logic.
2023-08-17 20:42:17 -07:00
tastybento 1228da131f
Merge pull request #2167 from BentoBoxWorld/ready_commands
Adds a config section in BentoBox to run commands when it is loaded
2023-08-15 17:26:34 -07:00
tastybento 08d73f232b Adds a config section in BentoBox to run commands when it is loaded
Commands are run as console.
2023-08-15 17:25:26 -07:00
tastybento 06ccb8a5e0 Fixed issue with demoting or promoting above yourself.
Addresses #2164

Added test class to check command. Added new error text.
2023-08-10 21:57:41 -07:00
tastybento 768988e803 Revert "Update ADDON.md - added Parkour"
This reverts commit fd91ab596b.
2023-08-06 09:18:45 -07:00
tastybento fd91ab596b
Update ADDON.md - added Parkour 2023-08-06 09:16:15 -07:00
tastybento 397f9e4700 Added more JavaDoc for API usage 2023-08-01 09:43:47 -07:00
tastybento 1b2748f920 Version 1.24.2 2023-08-01 09:43:31 -07:00
tastybento 78092aa3e9
Merge pull request #2158 from BentoBoxWorld/develop
Version 1.24.1
2023-07-29 11:40:08 -07:00
tastybento 475e67c447 Revert "Refactor YamlDatabaseHandler to reduce continue statements"
This reverts commit fec43adc35.
2023-07-24 18:27:06 -07:00
tastybento 2ad7796582 Refactor to reduce complexity 2023-07-22 16:20:21 -07:00
tastybento 3e1dc81592 Minor code smell reduction 2023-07-22 16:03:55 -07:00
tastybento fec43adc35 Refactor YamlDatabaseHandler to reduce continue statements
in a loop to just one.
2023-07-22 15:52:15 -07:00
tastybento 7a21aba3a8 Added more JavaDoc to ConfirmableCommand 2023-07-22 12:00:43 -07:00
tastybento 62a3a8d6fa Fixed NPE when getting UUID for unknown name.
Added test methods for PlayersManager
2023-07-22 12:00:10 -07:00
tastybento 1a0a314ece Added ClosestSafeSpotTeleport class
Removed code from the main class that was not being used. e.g.,
failureRunnable.
2023-07-20 15:09:14 -07:00
tastybento 2512a892fd Added clearInline Mocks just in case. 2023-07-20 00:38:58 -07:00
tastybento 850939f3a8 Fix issue were glow was not removed.
If glow was applied to a panel item, but then the glow setting was set
to false, the glow was not removed from the panel item.

Relates to https://github.com/BentoBoxWorld/AOneBlock/issues/326
2023-07-20 00:27:27 -07:00
tastybento 8ce78c8fe0 Added DefaultPasteUtil test class 2023-07-15 17:55:48 -07:00
tastybento 2dfd136591 Added test class 2023-07-15 16:55:59 -07:00
tastybento 357a8fdc5f Added test class for BlueprintEntity 2023-07-15 16:45:51 -07:00
tastybento a0b349ec27 Complexity reduction 2023-07-15 16:27:31 -07:00
tastybento 585f720f6f Reduce complexity 2023-07-10 22:48:33 -07:00
tastybento a6bb78c80f Refactor to reduce complexity 2023-07-10 22:25:30 -07:00
tastybento c5baa1d0eb Refactor to reduce complexity 2023-07-10 22:08:02 -07:00
tastybento 988ace8329 Update to latest Jacoco 2023-07-10 21:18:58 -07:00
tastybento 170c31fcec Judge location of damage by location of entity not user 2023-07-09 15:07:37 -07:00
tastybento 211d636bfb Remove dependency on apache util 2023-07-08 15:02:50 -07:00
tastybento 4ef974fbde Better error for unknown icon material, and test class 2023-07-08 08:52:20 -07:00
tastybento 9a464ab795 Minor refactor - use containsKey method. 2023-07-08 08:39:56 -07:00
tastybento 39a6fc5006 Refactor, use if statements instead of switches 2023-07-08 08:39:00 -07:00
tastybento 325c9c2351 JavaDoc fix 2023-07-08 08:38:30 -07:00
tastybento 30d29c6ff9 Refactor: Merge switch values 2023-07-08 08:38:20 -07:00
tastybento 2c8db2a797 Use any() which now is a varArg tester. Remove deprecation 2023-07-08 08:37:10 -07:00
tastybento df45fca562 Refactor - merge List creation and addAll into one 2023-07-08 08:36:29 -07:00
tastybento df6fb7d716 Fix JavaDoc 2023-07-08 08:35:31 -07:00
tastybento 54139b60f0 Remove unneeded return 2023-07-08 08:35:22 -07:00
tastybento 74a2e9d3c8 Change OP to op 2023-07-08 08:33:51 -07:00
tastybento 5953df9dbb Version 1.24.1 2023-07-08 08:33:24 -07:00
tastybento 363b23a049
Merge pull request #2152 from BentoBoxWorld/develop
Version 1.24.0
2023-07-03 16:27:37 -07:00
tastybento 9334858675 Added IslandDeletehomeCommand test class 2023-07-03 16:11:37 -07:00
tastybento 7d25aff28f Added test class for DefaultPlayerCommand 2023-07-02 15:52:20 -07:00
tastybento f47e87af82 Added test class for PanelItem 2023-07-02 15:10:23 -07:00
tastybento 6d3349a0f9
Merge pull request #2153 from BentoBoxWorld/delete_manager_refactor
Put island deletion under one class manager
2023-07-01 13:18:28 -07:00
tastybento 9bd0104a3d Put island deletion under one class manager
The goal is to make BentoBox less of a monster class and have fewer
dependent classes.
2023-07-01 12:49:18 -07:00
tastybento 7b8e18afff Adds Chiseled Bookshelf protection #2136 2023-07-01 10:04:05 -07:00
tastybento f23ebec34e Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2023-06-30 18:36:06 -07:00
tastybento 9c6cc78c32 Adjust priority of PlayerInteraction event listener to NORMAL
This may help avoid mob shop clashes.
2023-06-30 18:35:55 -07:00
tastybento c28ecb1c74 Add null check. Not really needed but make Sonar happy. 2023-06-29 17:29:04 -07:00
tastybento cbed9ac831
Merge pull request #2150 from BentoBoxWorld/mv_remove_on_disable
Unregisters worlds with MV on shutdown.
2023-06-28 21:03:09 -07:00
tastybento e846d26fab Fix test
Test doesn't do anything though...
2023-06-28 21:02:39 -07:00
tastybento 4c0cb9f17c Unregisters worlds with MV on shutdown.
Fixes #2149
2023-06-26 22:08:49 -07:00
tastybento 0038c3f2b7 Remove boolean return for listener. Check event cancelation instead. 2023-06-24 15:22:23 -07:00
tastybento 66d27e7070
Merge pull request #2148 from BentoBoxWorld/sculk_cal
Adds calibrated sculk sensor to protection
2023-06-24 12:55:36 -07:00
tastybento 4b0c5ec894
Merge pull request #2147 from BentoBoxWorld/suspicious_brushing
Protect against brushing
2023-06-24 12:12:00 -07:00
tastybento 1a416d9a23 Fixes #2137 adds calibrated sculk sensor and test class 2023-06-24 12:11:08 -07:00
tastybento 74e053ef8a Protect against brushing
Fixes #2138
2023-06-24 11:31:14 -07:00
tastybento 2ed2492ce3
Merge pull request #2146 from BentoBoxWorld/Ignore_waxed_signs
If sign is waxed (not editable) then no check is required
2023-06-24 11:13:33 -07:00
tastybento 2b4e1dd5eb Update to 1.21.1 to get isWaxed API
Remove 1.20 from server compatibility as a result.
2023-06-24 11:11:51 -07:00
tastybento 89a9b13d81 Hidden commands can be seen by console, but are hidden from players 2023-06-24 10:40:44 -07:00
tastybento b50063685f If sign is waxed (not editable) then no check is required 2023-06-24 10:31:51 -07:00
tastybento 01313def01
Merge pull request #2139 from ThexXTURBOXx/patch-1
Update GitHub actions to NodeJS 16
2023-06-24 10:17:12 -07:00
tastybento 2805e5889b
Merge pull request #2141 from BentoBoxWorld/bbox_perms_command
Bbox admin perms command
2023-06-24 10:16:07 -07:00
tastybento a7ffb79f1d
Merge branch 'develop' into bbox_perms_command 2023-06-24 10:15:34 -07:00
tastybento 0856d48470 Proactively clear Users before tests just in case. 2023-06-24 10:13:11 -07:00
tastybento 4d295068cf Fix onlyConsole usage and added test class 2023-06-24 09:45:40 -07:00
tastybento bcb4ed28b8
Fixes #2142 (#2144)
Fixes the logic and adds a bunch of tests to prove the logic works.
2023-06-21 08:15:53 +03:00
tastybento d6d86d39d2
Merge pull request #2143 from BentoBoxWorld/register_other_worlds
Add API to enable gamemodes to register ownership over additional worlds
2023-06-20 21:06:14 -07:00
tastybento c6a8f7c095 Add API to enable gamemodes to register ownership over additional worlds 2023-06-20 21:03:47 -07:00
tastybento 372f3f14c6 Rename getter to be more generic 2023-06-19 09:45:09 -07:00
tastybento 393a0131f9 Improved IslandInfoEvent to include the calling addon.
This makes it easier for listeners to determine if they should react or
not.
2023-06-19 09:38:49 -07:00
tastybento c0beba7467 Formatting 2023-06-19 09:38:00 -07:00
tastybento 5c2166fc93 Add some NPE protection for variable substitutions 2023-06-19 09:37:14 -07:00
tastybento a90a00b09b Fix new permission tests 2023-06-19 09:36:45 -07:00
tastybento 09c60f24cf Add IslandInfoEvent to allow addons to add to the island info command 2023-06-18 20:58:52 -07:00
tastybento b512975b13 Updated BentoBox perms in plugin.yml to add perm command 2023-06-18 13:55:50 -07:00
tastybento e9067cfb28 Add bbox admin perms command and adjust perms for commands
Made some admin commands have their own perm so that they can be
controlled  individually by permissions.
2023-06-18 13:55:21 -07:00
tastybento 5a52978803 Add perms test for admin teleport command 2023-06-18 13:35:16 -07:00
tastybento b64015e3b6
Update ServerCompatibility.java
Added 1.21.1
2023-06-15 18:43:17 -07:00
Nico Mexis c000da6e6f
Update GitHub actions to NodeJS 16 2023-06-11 18:50:48 +02:00
tastybento 2672ee5d1b Handle Boats and other Materials for 1.20. Uses Tags a lot.
May break compatibility with older server versions.
2023-06-10 21:31:39 -07:00
tastybento d25d1713f8 Adds protection for sign editing for 1.20
Fixes #2135
2023-06-10 15:49:10 -07:00
tastybento 6932ce3759 Added support for 1.20 2023-06-10 10:03:17 -07:00
tastybento 38ddeff369
Merge pull request #2133 from BentoBoxWorld/2132_IslandNameEvent
Add new IslandNameEvent and test class for command
2023-06-06 21:00:36 -07:00
tastybento e4a3891590 Add new IslandNameEvent and test class for command 2023-06-06 20:38:59 -07:00
tastybento 7bb1907897 Version 1.24.0 2023-06-06 18:41:06 -07:00
tastybento 5f6b91bc64 Version 1.23.3 2023-05-28 12:11:27 -07:00
tastybento fdc03df298
Merge pull request #2129 from BentoBoxWorld/develop
Version 1.23.2
2023-05-28 11:52:07 -07:00
tastybento eb76d00f80
Merge branch 'master' into develop 2023-05-28 11:39:17 -07:00
tastybento a7b1028496
Merge pull request #2128 from DustyGanteng/DustyGanteng-patch-1
Update en-US.yml
2023-05-28 11:37:01 -07:00
tastybento 9ffc838f2d
Merge pull request #2125 from zrdzn/patch-1
Fix typo in config
2023-05-28 11:35:40 -07:00
Dusty d0d01dbc2b
Update en-US.yml
Fixed weird remove reset count  message
2023-05-21 23:42:47 +07:00
tastybento 8f954090e7 Fixes VisitorKeepInventory to disregard coops and trusted
#2126
Also fixes some tests.
2023-05-13 09:20:47 -07:00
zrdzn 84adfaa622
Fix typo in config 2023-05-04 13:48:53 +02:00
tastybento f05d4dc57f Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2023-04-22 17:43:41 -07:00
steve4744 e991f09921
replace hardcoded file separator (#2121) 2023-04-21 13:05:00 +03:00
tastybento f52150ec1d Version 1.23.2 2023-04-18 16:22:15 -07:00
tastybento ce5830e2ad
Merge pull request #2120 from BentoBoxWorld/develop
1.23.1 release - hot fix
2023-04-18 16:20:00 -07:00
tastybento 577e476796 Remove console spamming debug. 2023-04-18 16:18:09 -07:00
tastybento 8d0fcf0169 Version 1.23.1 2023-04-18 14:33:20 -07:00
tastybento 0ee9ff4f10
Merge pull request #2118 from BentoBoxWorld/develop
Version 1.23.0
2023-04-15 11:03:45 -07:00
tastybento 84e6523b9d Only disable Addons, not Pladdons
Plugin disabling is done by the server.

Fixes #2117
2023-04-08 16:20:35 +01:00
tastybento 5c5077a6b5 Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2023-04-08 15:49:36 +01:00
tastybento 9ee5bcbcf2 2023 2023-04-08 15:49:27 +01:00
BONNe c235abbfa6
Disable chunk generation on rengeneration (#2114)
The SimpleWorldRegenerator generated world new chunks that were not generated before. 
It is not necessary, as regeneration should happen only on used chunks.
2023-04-07 14:39:27 -07:00
tastybento b4737f104c Implements HARVEST and CROP_PLANTING protection flags
Requested by #2113
2023-04-07 20:51:59 +01:00
tastybento ba19b08f4c Version 1.23.0 2023-04-07 17:28:02 +01:00
tastybento 073f3a49d5
Adds API to enable translations to be set and specific API for flags (#2109)
This was a request on Discord so that plugins can set flags and set the
name and lore for them too.
2023-03-26 10:06:38 -07:00
tastybento 1a293a6936
Implements NMS for pasting and supports older versions of server (#2110)
* Implements NMS for pasting and supports older versions of server

* Remove trial code that shouldn't have been committed.

* Remove trial code that shouldn't have been committed.

* Remove 1.19.3 support.

* Use 1.19.4 in POM
2023-03-26 10:05:54 -07:00
tastybento 85a7375004 Remove warnings on loading addons (not Pladdons) 2023-03-25 15:50:39 -07:00
tastybento 25e601b26c Now with proper commit... 2023-03-25 09:08:34 -07:00
tastybento 907268c2f4 Use Bukkit loadPlugin for loading Pladdons
Provides compatibility with Paper. Ensured bytecode rewriting is done
when code is loaded.
2023-03-25 09:08:18 -07:00
tastybento 6894695024 Clean up test classes; use latest API 2023-03-23 11:02:08 -07:00
tastybento 233e5ceb65 Add SHULKER_TELEPORT flag English text. 2023-03-19 18:49:16 -04:00
tastybento 5d67dbe5e3 Javadoc clarification 2023-03-18 17:46:56 -04:00
tastybento 2cb561b943
Update to 1.19.3 for NMS (#2107)
* Update to 1.19.3 for NMS

Clean up some imports and deprecation notifications.

* Added 1.19.4 to compatible servers
2023-03-18 10:28:19 -07:00
tastybento ca604f893c
Relates to #2104. Adds user.displayName() as a placeholder option (#2108)
* Relates to #2104. Adds user.displayName() as a placeholder option

* Fix test
2023-03-18 10:27:50 -07:00
tastybento 3285ef1ad2 Fix and enhancement for #2105 2023-03-17 20:45:15 -07:00
tastybento 70f044265a Fix test - 54 flags 2023-03-13 20:42:26 -07:00
tastybento 2940d2e492 Add cheat flag to prevent Endermen from teleporting
Fixes #2105
2023-03-13 20:17:03 -07:00
tastybento c31458f8e6 Remove offending blueprint original particle shower which doesn't work
Never did anything useful anyway. Fixes #2100
2023-03-04 23:36:32 -08:00
tastybento 2b40ae47d7 Update Spigot version 2023-03-04 23:35:39 -08:00
tastybento 2c283ae3a9
Javadoc and other cleanup from code analysis (#2099) 2023-02-26 17:48:37 -08:00
tastybento f02b4d5011
Merge pull request #2096 from BentoBoxWorld/void_world_deletion 2023-02-26 07:14:39 -08:00
tastybento 6925a788f3 Put back the deprecated way to delete and adds a GameModeAddon API
to declare which type to use. AcidIsland will need updating to use this
API.
2023-02-24 17:33:53 -08:00
tastybento 1b81e74f4c Evaluates banned commands with spaces
Addresses #2092
2023-02-20 09:59:26 -08:00
tastybento 999ca242d7 Use Eclipse NonNull annotation instead of JetBrains NotNull 2023-02-19 08:41:21 -08:00
tastybento 7d0eb3586d Make events public so they can be instantiated in 3rd party JUnit tests
I need to create "fake" events to test addons but unless they are public
I cannot. All Bukkit events are public, so this should be fine.
2023-02-15 16:01:52 -08:00
tastybento a713b7be07 Version 1.21.1 2023-02-12 15:21:32 -08:00
tastybento fe888a8d0e
Merge pull request #2069 from BentoBoxWorld/develop
New version
2023-02-12 15:00:01 -08:00
tastybento 45f2dca414 Fixes max team size checks.
Fixes #2034
2023-02-12 12:08:38 -08:00
tastybento 8276406f9f Fix runCommands method
Note to future self, don't remove Util methods because someone might be
using them!
2023-02-12 08:22:22 -08:00
tastybento 5eaf54e905 Added AdminBlueprintLoadCommand test class 2023-02-11 15:47:52 -08:00
tastybento 04545e676f AdminBlueprintDeleteCommand test class. 2023-02-11 14:57:42 -08:00
tastybento 08a1ad8126 Added AdminBlueprintCopyCommand test class 2023-02-11 14:46:34 -08:00
tastybento 79d56a5cef Added test class for AdminBlueprintSaveCommand 2023-02-11 12:03:16 -08:00
tastybento f93c49184f Added AddonDescription test class 2023-02-11 10:11:51 -08:00
tastybento 0f0b5aacc2 Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2023-02-11 09:09:36 -08:00
tastybento e978b29d85 Bug fix: Use same seed for seed world as main world. 2023-02-11 09:09:25 -08:00
tastybento 6c1bcdec05 Added test class for IslandChunkDeletionManager 2023-02-08 20:23:19 -08:00
tastybento a793a33758 Minimal test class for BlueprintPaster 2023-02-08 19:53:16 -08:00
tastybento 1783bb7058 Fixes bug where pasting was happening twice 2023-02-08 19:21:50 -08:00
tastybento a703499817 Added test class. 2023-02-08 19:16:32 -08:00
tastybento 224be307a0 Fix JavaDoc errors 2023-02-08 18:40:03 -08:00
tastybento b62a1862a2 Add ${argLine} to surefire plugin
This should fix the coverage reports.
2023-02-08 17:17:12 -08:00
JamesMCL44 3623537d84
Update zh-HK.yml (#2085)
Update to latest locale for latest plugin version
2023-02-06 12:56:02 -08:00
tastybento 6c8ccd2a57 Attempt to get coverage report from SonarCloud 2023-02-05 17:23:52 -08:00
tastybento f1eaa0fcde Refactor to reduce complexity 2023-02-05 16:23:23 -08:00
tastybento 9ccdcceaba Refactor to reduce complexity 2023-02-05 16:19:05 -08:00
tastybento 35ece03e5b Refactor to reduce complexity. 2023-02-05 16:08:23 -08:00
tastybento 760bc2e086 Define a constant instead of duplicating literals 2023-02-05 16:03:43 -08:00
tastybento caf994738e Defined a constant instead of duplicating "CHEST" 7 times 2023-02-05 16:00:06 -08:00
tastybento f00320095d Added since and forRemoval to Deprecated annotations. 2023-02-05 15:55:51 -08:00
tastybento 46d20f34ff Remove statements that are always true 2023-02-05 15:50:04 -08:00
tastybento df910e96d3 Refactor to reduce complexity. 2023-02-05 15:46:32 -08:00
tastybento f29e78557a Change location of seed world data.
Fixes #2087
2023-02-05 14:11:59 -08:00
tastybento 2d8b3074a9 Minor refactor to remove continue from for loop 2023-02-05 10:39:10 -08:00
tastybento ad2541963e Minor instanceof casting change 2023-02-05 10:38:09 -08:00
tastybento 2c1a018071 Clarify nonnull on Location 2023-02-05 10:37:43 -08:00
tastybento c6fc42846a Remove unused imports 2023-02-05 10:15:34 -08:00
tastybento 0e253f3ba5 Clarify that parameter is non-null 2023-02-05 10:10:25 -08:00
tastybento a68436fde1 Remove Optional from parameter. 2023-02-05 10:05:57 -08:00
tastybento 1d28979d5c Refactor to reduce complexity 2023-02-04 19:08:09 -08:00
tastybento 187132bc1d Refactor to reduce complixty. Added comments. 2023-02-04 19:00:18 -08:00
tastybento 3331ffac4e Refactor to reduce complexity. 2023-02-04 18:49:13 -08:00
tastybento f9e57898ab Refactor to reduce complexity. 2023-02-04 18:45:05 -08:00
tastybento 0c29d0b5dd
Uses seed worlds for deletion purposes (#2084)
* Uses seed worlds for deletion purposes

* Use Flat type

* Implement the superflat saver

* Fix nullable issue

* Fix tests.

* Fix code smells
2023-02-04 18:37:06 -08:00
tastybento 7040047fcb Fix broken test for IslandRespawnListener 2023-02-04 10:37:02 -08:00
BONNe ea6ca3d26b
Add variable parsing on reference text (#2073)
This solves the issue in customizable GUI's where variables are defined directly into the panel button name and description instead of providing the link to the locale. 

Not all users want to specify all text into a locale, and they would not get parsed variables just because of that.
This change fixes it and it does not give any bad situations even if the reference is just missing text in the locale, as there will be nothing for parsing.
2023-01-31 18:03:37 +02:00
tastybento a604d5cf82
Add [owner] in command placeholders #2080 (#2081) 2023-01-31 07:13:01 -08:00
tastybento bbb8207cf0 Teleportation was processed for entities even if it was switched off
Addresses #2078

Added test class to cover some of the logic in the teleportation class.
More tests can be written to cover the various outcomes.
2023-01-28 16:04:52 -08:00
BONNe e89970b49b
Remove 1.18.2 from supported version list. (#2074)
* Remove 1.18.2 from supported version list.

1.18.2 support was removed with changes in commit 056cff4b6f

Also, mark 1.19, 1.19.1 and 1.19.2 as incompatible. It happens because of GSon library changes that prevents bentobox compiled with Spigot 1.19.3 to run on older versions.

* Fixes GSON crashes introduced by migrating to 1.19.3 api.
2023-01-06 18:29:37 +02:00
tastybento 0de2c3a7b6 Update JavaDoc to indicate return value is immutable 2023-01-05 21:30:19 -08:00
tastybento 0f89de6c2a Make list mutable. 2023-01-05 21:16:50 -08:00
tastybento 8e40bf9dcf Added more validation for addon.yml and test class 2023-01-02 09:31:16 -08:00
tastybento 10a73e66b4 Added explicit @since annotation 2023-01-01 09:25:09 -08:00
tastybento 3746349e22 The protected modifier for constructor should be enough.
Constructors of abstract classes can only be called in constructors of
their subclasses. So there is no point in making them public.
2023-01-01 09:22:41 -08:00
tastybento 0e5db0f41f Quiet excessive warnings from JavaDoc plugin 2023-01-01 09:19:20 -08:00
tastybento 1262a08a22 Removed deprecated method isPlayer(User) 2023-01-01 08:43:31 -08:00
tastybento f545a4576e Remove unused comment 2023-01-01 00:19:10 -08:00
tastybento ab434b63fa Completed tests for User class 2023-01-01 00:17:30 -08:00
tastybento 6b88d31762 Bug fix for particle validation 2023-01-01 00:02:59 -08:00
tastybento 93ca9fb0e6 Correct nullable tags 2022-12-31 18:33:49 -08:00
tastybento 0bffe3806a Fix tests 2022-12-31 18:19:32 -08:00
tastybento 872940011a Loads of minor code smell fixes
Mostly replacing instanceof check and cast with e.g. 'instanceof Player
player'
Replaced some "switch" statements by "if" statements to increase
readability.
Used the primitive boolean check to avoid potential NPEs.
Reordered field modifiers to comply with the Java Language
Specification. Mainly @NonNull position.
2022-12-31 18:19:25 -08:00
tastybento 056cff4b6f
Code clean up from Sonar Cloud analysis (#2068)
* Code clean up from Sonar Cloud analysis

* Fix tests

* Remove code smell

* Rename "island" which hides the field declared at line 25.

* Removed code smells.

* Rename variable record to rec

Renamed "record" variable to not match a restricted identifier.
Restricted Identifiers should not be used as identifiers. "record" is
using in Java 16.

* Added private constructor to prevent instantiation of static class

Changed variable name to rec instead of restricted "record".

* Remove Blueprint code smells.

* Use a record for database settings constructor

Code smell: Methods should not have too many parameters. I'm not sure
what methods are using this class though.

* Update MyWorlds version

The POM for MyWorlds is invalid and causes a warning, but this still
persists with this version.

* Extracted nested try block into a separate method.

Makes it clear when reading the code what might be caught

* Extracted nested try block into a separate method.

* Fixed JavaDoc /** instead of just /*

* Extracted nested try block into a separate method.

* Refactored to not assign loop counter from within the loop body.

* Better delete option. With results.

That said, this is legacy code to handle an issue that occurred a long
time ago and this whole set of code can probably be removed.

* Catch Exceptions not Throwable

* Log error with BentoBox logError

* Use computeIfAbsent

Using these instead leads to cleaner and more readable code.

* User can no longer be null

* Added the missing @Deprecated annotation and @since ref

* Added @since reference

* Merge if statements

* Use BentoBox error logging.

* Added JavaDoc @since

* Remove deprecated class and move used class

* Remove deprecated WoodType and use Type.

* Remove unused import

* Extracted nested try block into a separate method.

* Comment empty default statement

* Clean up logic; avoid switch

* Use Java instead of Guava

* private constructor to hide the implicit public one.

* Private constructor to hide the implicit public one.

Merged if statement.

* Add comment

* if merge

* Make variable constant

* Remove unused imports

* Remove deprecated and unused method

* Remove unused import

* Typo

* Remove instanceof and cast

* Remove superfluous null check

* Put constant at bottom of file because @BONNe likes it there.

* Simplify particle validation code
2022-12-31 16:41:17 -08:00
tastybento 0183380b82 Edited US English text entries
Supercedes #1033
2022-12-30 11:22:01 -08:00
tastybento d541e7054c Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2022-12-30 11:20:31 -08:00
tastybento 36ae34da5d
2049 trees growing outside range (#2050)
* Fixes #2046. Handles null players for placeholders.

* Remove unused import

* Fixes #2049 for adjacent island spaces

This also deletes blocks in the event rather than setting them to AIR as
was done previously.
2022-12-30 09:37:18 -08:00
tastybento a7869832f1 Merge branch 'develop' of https://github.com/BentoBoxWorld/BentoBox.git into develop 2022-12-29 11:31:18 -08:00
tastybento 18983e5570
Code cleanup (#2065)
* Cast operands to (double)

* Code clean up from static analysis

* Revert the GitHubAPI version change because it didn't work.
2022-12-29 10:01:25 -08:00
tastybento ebd9215c56 Cast operands to (double) 2022-12-28 12:56:05 -08:00
tastybento 70262be896 Update to latest AuthLib 2022-12-28 12:42:58 -08:00
tastybento bde294e0e6 Updated some dependency versions
https://github.com/BentoBoxWorld/BentoBox/issues/2064
2022-12-28 12:37:33 -08:00
tastybento e7f1269443
Support for 1.19.3 (#2060) 2022-12-11 17:51:52 -08:00
tastybento e2b5545f3f Expand remove mobs world setting to include respawning protection
Addresses #2058

We already have a option to protect against hostile mobs when logging in
or teleporting, but not respawn. This adds protection to that. Admin can
turn off the option if they want it more hard core.
2022-12-04 21:06:14 -08:00
tastybento 69fa951907 Handles null player world better on joining server
In theory, this should not be required, but it seems that a player's
world might be null in some edge cases. Alternatively, the Util.getWorld
function can return a null if the player was in a nether or end but that
does not have a corresponding overworld.
Addresses #2057
2022-12-04 20:36:41 -08:00
tastybento 7aa1e2da89 Fix to handle kelp and bamboo spread.
Addresses #2053. This is actually due to a server bug
https://hub.spigotmc.org/jira/browse/SPIGOT-5312. This change uses the
BlockSpreadEvent to trap kelp and bamboo and bamboo sapling.
2022-11-26 17:36:19 -08:00
tastybento 2629e940c7
2046 null player placeholder support (#2048)
* Fixes #2046. Handles null players for placeholders.

* Remove unused import
2022-11-13 08:49:22 +02:00
tastybento 1c4fb76828
Fixes #2045. Hides invisible team members from the count. (#2047) 2022-11-12 02:28:09 +02:00
BONNe acb984ee90
Fixes super-flat clearing
Superflat was not working properly. It was always clearing just 1 (first) chunk because it referenced the chunk from the first event that created the clearing task.
The issue should be fixed.
2022-11-01 23:04:59 +02:00
BONNe d9ad9f158b
Fixes a mistake in clean super flat listener
There was a logical mistake in checking if a clean super flat should work.
2022-11-01 20:10:45 +02:00
BONNe 87a6799cd1
Fixes clean super-flat issue from 1.19 worlds
In 1.19 worlds it was fixed that super-flat worlds are generated at min-height, instead at 0.
This change keeps the previous super-flat chunk generation until 1.18 is dropped.
2022-11-01 18:25:39 +02:00
tastybento d50208c0e1
Add and fix tests for end gateway and portal. (#2044) 2022-10-31 17:59:04 -07:00
BONNe a22ce33175
Mark end portals and gateways as unsafe location. (#2042)
The end portals and gateways teleports players to a different dimension upon player touches it. So teleportation should consider these positions as unsafe, otherwise player will be teleported to the different dimension instantly.

Fixes #2040
2022-10-19 11:41:00 +03:00
Irmo van den Berge ac4922534e
Register BentoBox World Generator option with MyWorlds (#2039)
Signed-off-by: Irmo van den Berge <irmo.vandenberge@ziggo.nl>
2022-10-19 11:27:12 +03:00
BONNe b48a5a73b8 Merge remote-tracking branch 'origin/master' into develop-fix
# Conflicts:
#	pom.xml
2022-10-11 09:55:16 +03:00
BONNe 065f4ba4bd Spigot does not support parallel stream processing for placing blocks and entities in the world.
This is a hot-fix release.
2022-10-11 09:37:59 +03:00
BONNe e47890c16e Merge remote-tracking branch 'origin/develop' into develop 2022-10-04 15:54:34 +03:00
BONNe 866dff6244 Fixes missing FLAG check on clicking minecart entities
Players could open minecart entities, even without flag requirement.

Fixes #2031
2022-10-04 15:54:18 +03:00
BONNe 369001d368
Rework SQL database implementation. (#1993)
This change switches from plain JDBC driver implementation to a HikariCP Pool implementation.

Pool Implementation is complete for PostgreSQL and SQLite, while MariaDB and MySQL implementation still uses JDBC drivers, however with HikariCP pools.

Also, I added extra properties for SQL databases, where users could specify their own datasource properties.
2022-10-04 12:31:51 +03:00
BONNe 4792ff3f62 Init version 1.22 - Customizable GUI's version. 2022-10-04 12:11:17 +03:00
BONNe 1a59ca7785 Merge remote-tracking branch 'origin/develop' into master
# Conflicts:
#	pom.xml
#	src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
#	src/main/java/world/bentobox/bentobox/database/objects/Island.java
#	src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java
2022-10-04 11:56:32 +03:00
BONNe b263c92c13
Use Spigot only events. (#2029)
I missed that EntityMoveEvent is only Paper. Interesting why Spigot does not have such...

Fixes #2028
2022-10-01 01:50:32 +03:00
BONNe 52bca66149 Add better config description for safe-spot-search-range. 2022-09-30 21:46:01 +03:00
BONNe f01e5540c7 Deprecate old player teleportation code. 2022-09-30 21:41:02 +03:00
BONNe bf87cca754 Implement proper entity teleportation between dimension.
Teleportation via portals for entities was in a mixed state. It was not fully implemented and not fully prevented. Especially when portal linking was enabled.

Now I implemented world settings flag: ENTITY_PORTAL_TELEPORT.
Enabling this flag will allow entities to use portals to switch dimensions.

Fixes #2023 and #966
2022-09-30 21:33:17 +03:00
BONNe 682d835961 Add all dimension checks for methods. 2022-09-30 14:34:28 +03:00
BONNe 4458d16274 Fixes incorrect condition for missing island check. 2022-09-30 14:34:12 +03:00
BONNe d197ce8bea Add class description. 2022-09-30 02:34:46 +03:00
BONNe aa7abe02bf Teleport island-less player to the spawn if he exits nether 2022-09-30 02:33:07 +03:00
BONNe 97c4cf883f Implement proper end exit portal handling.
When players will exit the end-gate, they will be teleported to proper island, instead of world spawn point.

The only way how players can be teleported to the spawn is if they do not have islands.
2022-09-30 02:25:44 +03:00
BONNe eb5147e710 Change to direct PlayerTeleportListener#portalProcess method call. 2022-09-30 01:31:05 +03:00
BONNe 755aeb866e Rework player teleportation.
I reworked the classes and some teleportation operations. Now teleportation should find the closest available spot, instead of always being the highest block at original Y location. Part of #1994.
Also, I fixed an issue that portals stopped working if some conflicting options were enabled. Now portals will not work only if nether is disabled in config.
2022-09-30 01:26:31 +03:00
BONNe 173808b787
Adds glow berries protection. (#2027)
Uuups... someone forgot to add them.

Fixes #2020
2022-09-29 18:58:53 +03:00
BONNe 35ce1a7d81
Rework blueprint and blueprint bundle names. (#2026)
* Fixes issue when blueprint clipboard was stuck after saving.

The issue was that Map#putIfAbsent still creates a new task, even if object is already in map. The usage requires to use Map#computeIfAbsent.

* Rework blueprint and blueprint bundle names.

There was an issue with using non-english characters in blueprint names. It was not possible, as all chars for names were converted to lower cased english letters. It included display names.

I reworked it a bit and now it should be possible to set non-english names for bundles and blueprints.

Fixes #1954
2022-09-29 18:44:07 +03:00
Huynh Tien 12926f9ee7
setBlock & setEntity as CompletableFuture (#2019)
* setBlock & setEntity as CompletableFuture

* use collectingAndThen toArray
2022-09-05 09:28:16 +03:00
BONNe 9088ea4b80 Fix failing unit-test 2022-08-26 12:43:05 +03:00
BONNe 50bc236bbc Fix failing unit-test 2022-08-26 12:27:11 +03:00
BONNe c7b48b3d2a
Update Spigot 1.19.2. (#2017)
Add 1.19.2 compatibility.
2022-08-26 12:17:19 +03:00
BONNe 6f791420e7
Proper dimension processing (#2015)
* Adds ProtectionBoundingBox for environment

Since minecraft introduced different island heights for different dimensions, protection bounding box were returned wrong area for nether and end dimensions.
This change adds a new method that allows to get proper protection bounding box for requested dimension.
Due to the fact, that nether and end islands can be disabled, then this method may return null.

Fixes #2014

* Adds BoundingBox for environment

Since minecraft introduced different island heights for different dimensions, bounding box were returned wrong area for nether and end dimensions.
This change adds a new method that allows to get proper bounding box for requested dimension.
Due to the fact, that nether and end islands can be disabled, then this method may return null.

Part of #2014

* Fixes Island#onIsland check for non-island worlds

Island#onIsland method was missing checks if island mode is enabled for requested dimension. It returned false positive situations in cases when island generation were disabled in nether or the end worlds.

* Fixes Island#inIslandSpace check for non-island worlds

Island#inIslandSpace method was missing checks if island mode is enabled for requested dimension. It returned false positive situations in cases when island generation were disabled in nether or the end worlds.

* Adds some helper methods in Island object.

- Island#getNetherWorld - returns the nether world or null
- Island#getEndWorld - returns the end world or null
- Island#getWorld(Environment) - returns world of requested environment or null
- Island#isNetherIslandEnabled - returns if nether is generated and nether islands are enabled.
- Island#isEndIslandEnabled - returns if end is generated and end islands are enabled.
2022-08-26 11:32:03 +03:00
BONNe 6c1399c2b4
Fixes wrong admin permission.
Default Amin command should have `admin` instead of admin.*
2022-08-14 20:12:15 +03:00
BONNe 44201afa1f Fixes an issue with missing parent permission check before command execution.
Implement a new method that recursively checks if player has access permission to all commands in whole hierarchy.

Fixes #2010
2022-08-03 19:00:42 +03:00
BONNe 64b4c43742 Revert 6e734fc343
The commit was wrong. Protection range should not be decreased, as that would mean that max X and Z blocks are not included in island protection bounding box.
This change is the same as 3c65194dfb but was missed when it was fixed.

Relate to #473
2022-07-25 00:16:16 +03:00
BONNe a59697c14e Fixes particle spawning for older API usage.
Border addon used still older BentoBox API. This change fixes it, and fixes API compatibility with older addons/plugins.
2022-07-14 11:29:46 +03:00
BONNe 5a527d4119 Add parent for admin team's commands.
This is a parity change with player command.
2022-07-13 12:35:20 +03:00
BONNe aed8caeb76 Tweak translation for skulk sensor and shrieker. 2022-07-10 01:44:15 +03:00
BONNe 927fcba15a Disables biome copying by default in blueprints.
Biome will be copied only if it is required by copy command parameter.

Fixes #1862
2022-07-07 00:08:15 +03:00
BONNe 9ec8730359 Prevents liquids from being placed from dispenser outside island area.
Fixes #1869
2022-07-06 23:55:17 +03:00
BONNe 9643f617b6 Implement a command that allows to reset island name.
Fixes #1879
2022-07-06 23:30:51 +03:00
BONNe 3784aa48d7 Disables podzol and coarse dirt from becoming path.
This protection reuses Flags.COARSE_DIRT_TILLING.

Fixes #1878
2022-07-06 23:00:20 +03:00
BONNe 2f244c0606 Fixes an issue when portals did not operate under 0.
Fixes #1977
2022-07-06 22:12:47 +03:00
BONNe f969f2721f Disable DynmapHook as it is not implemented.
Fixes #1995
2022-07-06 20:39:15 +03:00
Galić Mijo cbd063c9e8
Check if unique name contains chars not supported in regex expression (#1998)
* Check if unique name contains chars not supported in regex expression
Cannot start, contain, or end with special char, cannot contain any numbers.
Can only contain - for word separation

* Negation fix
2022-07-06 20:21:36 +03:00
BONNe db323390cf
Implements adding unknown default island flag values in settings. (#2001)
* Implements option to define non-existing flags in WorldSettings.

This change adds 2 new methods in WorldSettings:
* WorldSettings#getDefaultIslandFlagNames
* WorldSettings#getDefaultIslandSettingNames

These methods replace getDefaultIslandFlags and getDefaultIslandSettings methods.
Default implementation just reads values from replaced methods.

Fixes #1830

* Implement conversion from flag id to actual flag object.

Replaces flag assignment to new island based on flag id's.

Fixes #1830

* Switch from Flag object to String object in Island class.

This switch allows to keep flags that are not present in current BentoBox installation. Otherwise, unknown flags may cause an issues.

Fixes #1830

* Implement FlagBooleanSerializer.

This serializer converts input map of String, Boolean to map of String, Integer.

This map is used to read island setting flags, and integer value is not classic boolean values. (0 for true and -1 for false).

Fixes #1830
2022-07-06 20:20:08 +03:00
BONNe d8fa029ac9
Fixes an issue where Blueprint Pasting did not send or include nether and the end blueprints times. (#1979) 2022-06-16 22:44:38 +03:00
Sliman4 3cd5b05130
Fix Actions (#1992)
* Move to new EngineHub maven repository

* Remove unused WorldEdit dependency

* Use Java 17 in GitHub Actions

* Also change maven-compiler-plugin and maven-javadoc-plugin versions to 17
2022-06-16 14:57:42 +03:00
BONNe 1991dc7236
Fixes failing test
Increase flag listener counter.
2022-06-14 09:09:06 +03:00
BONNe c54358441d
Raid abuse fix (#1991)
* Implements new VISITOR_TRIGGER_RAID flag.

This world settings flag allows toggling if visitors can or cannot start a raid on an island they are visiting.

Relates to #1976

* Fixes abuse of Raid Mechanism and Mob Natural Spawn Rules.

Fixes to #1976

* Simplify raid abuse detection.
2022-06-14 02:42:48 +03:00
BONNe e41f5ac24f
Fixes a bug that prevented mobs to attack
Commit 85b52f4b introduced a bug that prevented entities to target any island member. This should fix it.
2022-06-14 02:33:35 +03:00
BONNe d984955af3 Fixes Range Display command crashes.
Spigot in 1.18 introduce new way how block particles should be displayed. However, BentoBox never fully implemented it.
To avoid such issues in-future, I added checks for each particle type that requires extra data object.

Fixes #1989
2022-06-14 01:18:52 +03:00
BONNe 85b52f4bfb
Implement new 1.19 materials and entities (#1990)
* Update to Spigot and Paper API 1.19 and Java 17

* Set minimal version to 1.18.

* Add breeding protection for Axolotl and Goats (1.18)
Add breeding protection for Frog and Allay (1.19)

* Clean up switch statement in BlockInteractionListener

* Add chest boat support to BreakBlocksListener.

Part of #1987

* Add powdered snow pickup with bucket protection.

* Add glow ink sac protection on signs.
Switch to SheepDyeWoolEvent as it now contains player variable.

* Clean up Hurting Listener

* Mark 1.16.5, 1.17 and 1.17.1 as incompatible.
Mark 1.18, 1.18.1 and 1.18.2 as supported.

Add 1.19 as compatible.
Part of 1987

* Add ChestBoat inventory protection.
Part of #1987

* Clean up LockAndBanListener

* Add MANGROVE_PRESSURE_PLATE protection.
Part of #1987

* Add Glow Item Frame protection to player interact event.

* Clean up MobSpawnListener

* Clean up ChestDamageListener

* Change from custom method to Tag.PRESSURE_PLATES to detect pressure plates.
Part of #1987

* Implement proper chest boat protection.
Part of #1987

* Move to 1.19 R1 world regenerator.
Part of #1987

* Add allay to the animal entity list.
Part of #1987

* Add axolotl and other fish scooping protection.
Part of #1987

* Fixes Bucket and Glass Bottle filling.

Buckets and bottles were not working since cauldron splitting by type. This change fixes that.

It also protects from filling bottles with water from water sources or waterlogged blocks.

* Remove 1.17.1 compatibility check for biome adapter.
Add cheezee 1.19.1 compatibility version.

* Fixes chest boat interactions.

Part of #1987

* Implement Allay protection.
New flag ALLAY is required to interact with allays.

Part of #1987

* Prevent visitors for being targeted by entities if ENTITY_ATTACK is enabled.

* Implement Sculk Sensor and Shrieker activation protection setting.

Part of #1987

* Add music discs to the LangUtilsHook
Part of #1987

* Fixes failing unit-tests.
2022-06-14 00:38:53 +03:00
BONNe 6f2a9929c0
Fixes Glowing Item frames protection (#1985)
* Fixes glowing item frame protection.
Glowing item frames were not protected.

Fixes #475

* Fixes check-style.
2022-06-05 21:52:37 +03:00
BONNe aad10ad74f
Move database dependencies to the Spigot Libraries. (#1982) 2022-05-29 22:56:50 +03:00
BONNe 345b9e2564
Fixes an incorrect <Material>:CMD-<number> parsing
The issue was that it used an incorrect value for parsing Material. Instead of stripped part[0] it used whole text with CMD in it.
2022-05-23 23:23:22 +03:00
BONNe 3ab7ac8484
Implement ability to create ItemStacks with Custom Model Data. (#1981) 2022-05-23 20:46:02 +03:00
Huynh Tien 0f815d8175
More abstract on Blueprint Paster (#1970)
* more abstract on Paster

* comment and stuff

* How about Impl

* ok, PasteHandler

* PasteUtil

* don't check other BlockState if there is one matched

* world as an argument

* forgot the impl

* createBlockData from BlueprintBlock

* fix remaining conflicts

Co-authored-by: tastybento <tastybento@users.noreply.github.com>
2022-05-03 18:39:27 +01:00
evlad 4ab579f2cd
Feat: Filtering spectators from visitors (#1974)
* support 1.18 negative y

* feat: visitor gamemode spectator check
2022-05-03 08:03:49 +03:00
Huynh Tien 6fba4bfbea
More abstract on World Regenerator (#1969)
* more abstract on delete chunks

* update NMS to 1.18.2

* at most abstract, requires only the island and the world

* it's weird that we can't use whenComplete

* rename to WorldRegenerator
2022-05-02 15:23:31 -07:00
evlad d07c1b5a8c
support 1.18 negative y (#1973)
Fixes an issue where blueprint starting block could not be placed in negative area.
2022-05-01 08:44:42 +03:00
Huynh Tien 3e0368fbab
fix null on invitedPlayer (#1972) 2022-04-28 15:39:39 +03:00
BONNe 385aed02cd
Update to next BentoBox version 2022-04-26 12:07:44 +03:00
BONNe c4c51d00e2
Fixes wrong protection bounding box (#1971)
#1966 fixed a bounding box size for the whole island, while the protection bounding box was still wrong.

Fixes https://github.com/BentoBoxWorld/BSkyBlock/issues/473
2022-04-26 12:06:50 +03:00
tastybento 928f1eb1eb
Version 1.20.1 (#1966)
* Version 1.20.1

* Added name of the addon causing the issue.

https://github.com/BentoBoxWorld/BentoBox/issues/1944

* Use world min-height for island bounding box.

* Fixes a bug when fallback could not use reusable

There was an issue in PanelItemTemplate that prevented fallback buttons to be "reusable" things.
The issue was that reusable items were not passed to the panel item reader.

* Adjusted test to try to avoid errors

* Fix for random test failures.

* Added 1.18.2 support

* Address unnecessary PVP reports on each teleport (#1948)

If a player is teleporting on the same island in the same dimension, it keeps spamming that PVP is enabled in dimension. 
It should be enough with sending messages when the player teleports to the island.

Fixes #1885

* Fixes bug with Safe Spot Teleport (#1951)

There was a bug that prevented finding a safe spot if all valid blocks were in height with the `startY` location.

Reported via discord.

* Fix Exception error reported by IDE

I am not sure why Eclipse is saying this is an error.

* Fix for kicking offline players

https://github.com/BentoBoxWorld/BentoBox/issues/1950

* Add an option in SafeSpotTeleport to cancel if fail (#1952)

There was no option to cancel teleportation if SafeSpotTeleport could not find a valid spot. This option could be used to avoid creating "backup" blocks in situations when teleportation is avoidable, f.e. visiting an island.

* Replace peplaceAll with replace

It does the same thing if the first argument is not a regex.

* Use constants for common strings

* Use constants for common strings

* Go back to replaceAll

This is required.

* Clearer paster (#1953)

* WIP - make easier to understand.

* Small refactor of paster to make it easier to understand

* Fix tabs to spaces. Sorry - new editor!

* Fix tabs to spaces

* Fix tab to spaces

* Improve team kick command (#1957)

The kick command has an unnecessary owner check. As command should be configurable by island owners, then limiting it to an owner is wrong.
Add a code that allows kicking only lower-ranked players.

Add message that shows who kicked from the island.
Add message that shows that rank does not allow to kick.

* Solve crashes with Addon#allLoaded call (#1959)

If some addon has code in Addon#allLoaded that crashes the call, then it did not disable addon as well as did not call allLoaded for every other addon that was left in the list.

This should be solved by adding an extra try-catch.

* using java 16 syntax (#1958)

* Fixes kick command (#1960)

PR #1957 broke kick command and noone could kick players from teams.
This should fix it.

* Fixes a bug with blueprint height (#1961)

Blueprint clipboard was preventing setting Y below 0 or above 255. 

The code was not adjusted to 1.18 changes.
Reported via discord.

* Fixes Lava Duplication Glitch (#1964)

Due to the fact, that Obsidian Scooping uses one tick delay to remove obsidian, a player with a bucket in hand and offhand duplicated lava. 
To avoid that, added an extra check that ignores the interact event if a player holds a bucket in both hands, and interacted hand is offhand.

Fixes #1963

* Fixes failures in obsidian cooping listener. (#1965)

Failures happened after implementing #1964

Co-authored-by: BONNe <bonne@bonne.id.lv>
Co-authored-by: Invvk <70810073+Invvk@users.noreply.github.com>
2022-04-17 09:04:37 -07:00
524 changed files with 45883 additions and 17159 deletions

View File

@ -10,22 +10,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 16
uses: actions/setup-java@v2
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '16'
java-version: '21'
- name: Cache SonarCloud packages
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@ -38,7 +38,7 @@ jobs:
- run: mvn --batch-mode clean org.jacoco:jacoco-maven-plugin:prepare-agent install
- run: mkdir staging && cp target/*.jar staging
- name: Save artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: Package
path: staging

View File

@ -2,7 +2,6 @@
[![Discord](https://img.shields.io/discord/272499714048524288.svg?logo=discord)](https://discord.bentobox.world)
[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/BentoBox)](https://ci.codemc.org/job/BentoBoxWorld/job/BentoBox/)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=ncloc)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=security_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)
@ -112,9 +111,10 @@ repositories {
}
dependencies {
compileOnly 'world.bentobox:bentobox:PUT-VERSION-HERE'
compileOnly 'world.bentobox:bentobox:PUT-VERSION-HERE-SNAPSHOT'
}
```
**Note:** Due to a Gradle issue with versions for Maven, you need to use -SNAPSHOT at the end.
### History

250
pom.xml
View File

@ -63,29 +63,35 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>16</java.version>
<java.version>17</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<mongodb.version>3.12.8</mongodb.version>
<!-- Database related dependencies -->
<mongodb.version>3.12.12</mongodb.version>
<mariadb.version>3.0.5</mariadb.version>
<mysql.version>8.0.27</mysql.version>
<postgresql.version>42.2.18</postgresql.version>
<hikaricp.version>5.0.1</hikaricp.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.18-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.20.5-R0.1-SNAPSHOT</spigot.version>
<!-- Might differ from the last Spigot release for short periods
of time -->
<paper.version>1.16.5-R0.1-SNAPSHOT</paper.version>
<bstats.version>2.2.1</bstats.version>
<vault.version>1.7</vault.version>
<paper.version>1.20.6-R0.1-SNAPSHOT</paper.version>
<bstats.version>3.0.0</bstats.version>
<vault.version>1.7.1</vault.version>
<placeholderapi.version>2.10.9</placeholderapi.version>
<githubapi.version>d5f5e0bbd8</githubapi.version>
<dynmap.version>3.0-SNAPSHOT</dynmap.version>
<worldedit.version>7.2.5</worldedit.version>
<myworlds.version>1.19.3-v1</myworlds.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.20.1</build.version>
<build.version>2.4.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<server.jars>${project.basedir}/lib</server.jars>
</properties>
<!-- Profiles will allow to automatically change build version. -->
@ -121,6 +127,7 @@
<value>origin/master</value>
</property>
</activation>
<!--suppress CheckTagEmptyBody -->
<properties>
<!-- Override only if necessary -->
<revision>${build.version}</revision>
@ -138,6 +145,10 @@
</pluginRepositories>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
@ -146,10 +157,6 @@
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>placeholderapi-repo</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
@ -158,13 +165,9 @@
<id>dynmap-repo</id>
<url>https://repo.mikeprimm.com/</url>
</repository>
<repository>
<id>worldedit-repo</id>
<url>https://maven.sk89q.com/repo/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<!-- This is a temporary reference as the Maven Shade plugin
@ -176,44 +179,38 @@
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
<!-- Spigot NMS required for world regeneration :( -->
<repository>
<id>nms-repo</id>
<url>https://repo.codemc.io/repository/nms/</url>
</repository>
<!-- Used for MyWorlds hook -->
<repository>
<id>MG-Dev Jenkins CI Maven Repository</id>
<url>https://ci.mg-dev.eu/plugin/repository/everything</url>
</repository>
<!-- For MythicMobs -->
<repository>
<id>nexus</id>
<name>Lumine Releases</name>
<url>https://mvn.lumine.io/repository/maven-public/</url>
</repository>
<!-- For Multipaper -->
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<!-- Paper API -->
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
</dependency>
<!-- AuthLib. Used for Head Getter. -->
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>3.2.38</version>
<scope>provided</scope>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>${bstats.version}</version>
</dependency>
<!-- Mockito (Unit testing) -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<!-- Mockito (Unit testing) This goes at the top to ensure the dependencies are accurate. -->
<!-- This is required for PowerMockito to work and must be placed before it -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.30.2-GA</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
@ -225,16 +222,69 @@
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc....</groupId>
<artifactId>spigot</artifactId>
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc.</groupId>
<artifactId>spigot</artifactId>
<version>1.20.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc..</groupId>
<artifactId>spigot</artifactId>
<version>1.20.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc...</groupId>
<artifactId>spigot</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Paper API -->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>${bstats.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>${mongodb.version}</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP database handler -->
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
<scope>provided</scope>
</dependency>
<!-- Vault: as their maven repo is down, we need to get it from jitpack -->
<!-- See https://github.com/MilkBowl/VaultAPI/issues/69 -->
@ -259,9 +309,15 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-core</artifactId>
<version>${worldedit.version}</version>
<groupId>com.bergerkiller.bukkit</groupId>
<artifactId>MyWorlds</artifactId>
<version>${myworlds.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.lumine</groupId>
<artifactId>Mythic-Dist</artifactId>
<version>5.3.5</version>
<scope>provided</scope>
</dependency>
<!-- Shaded APIs -->
@ -273,7 +329,7 @@
<dependency>
<groupId>com.github.Marcono1234</groupId>
<artifactId>gson-record-type-adapter-factory</artifactId>
<version>0.1.0</version>
<version>0.3.0</version>
</dependency>
<!-- Static analysis -->
<!-- We are using Eclipse's annotations. If you're using IDEA, update
@ -298,13 +354,34 @@
<version>3.2.2</version>
<scope>provided</scope>
</dependency>
<!-- Spigot NMS. Used for chunk deletion and pasting. -->
<!-- Spigot NMS. Used for chunk deletion and pasting.-->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<!-- Slimefun -->
<dependency>
<groupId>com.github.Slimefun</groupId>
<artifactId>Slimefun4</artifactId>
<version>RC-37</version>
<scope>provided</scope>
</dependency>
<!-- ItemsAdder -->
<dependency>
<groupId>com.github.LoneDev6</groupId>
<artifactId>api-itemsadder</artifactId>
<version>3.6.3-beta-14</version>
<scope>provided</scope>
</dependency>
<!-- Multipaper -->
<dependency>
<groupId>com.github.puregero</groupId>
<artifactId>multilib</artifactId>
<version>1.1.13</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@ -346,7 +423,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>16</release>
<release>${java.version}</release>
<!-- <source>${java.version}</source> <target>${java.version}</target> -->
</configuration>
</plugin>
@ -354,35 +431,31 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!--suppress MavenModelInspection -->
<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.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/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/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.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
--add-opens=java.base/java.security=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
@ -394,10 +467,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.1</version>
<configuration>
<source>16</source>
<source>${java.version}</source>
<show>private</show>
<quiet>true</quiet>
<failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>
<!-- To compile with Java 11, this tag may be required -->
@ -430,9 +504,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.1-SNAPSHOT</version>
<version>3.4.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
@ -444,9 +519,13 @@
</relocation>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>world.bentobox.bentobox.paperlib</shadedPattern> <!-- Replace this -->
<shadedPattern>world.bentobox.bentobox.paperlib</shadedPattern>
</relocation>
</relocations>
<relocation>
<pattern>com.github.puregero.multilib</pattern>
<shadedPattern>world.bentobox.bentobox.multilib</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.apache.maven.shared:*</exclude>
@ -488,27 +567,34 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<version>0.8.10</version>
<configuration>
<append>true</append>
<excludes>
<!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>pre-unit-test</id>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
<configuration>
<formats>
<format>XML</format>
</formats>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -11,10 +11,8 @@ import org.bstats.charts.AdvancedPie;
import org.bstats.charts.SimpleBarChart;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bukkit.Bukkit;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.flags.Flag;
/**
* @author Poslovitch
@ -59,7 +57,6 @@ public class BStats {
registerGameModeAddonsChart();
registerHooksChart();
registerPlayersPerServerChart();
registerFlagsDisplayModeChart();
// Single Line charts
registerIslandsCountChart();
@ -171,27 +168,6 @@ public class BStats {
}));
}
/**
* Sends the "flags display mode" of all the online players.
* @since 1.6.0
*/
private void registerFlagsDisplayModeChart() {
metrics.addCustomChart(new AdvancedPie("flagsDisplayMode", () -> {
Map<String, Integer> values = new HashMap<>();
Bukkit.getOnlinePlayers().forEach(player -> {
Flag.Mode mode = plugin.getPlayers().getFlagsDisplayMode(player.getUniqueId());
if (values.containsKey(mode.name())) {
values.put(mode.name(), values.get(mode.name()) + 1);
} else {
values.put(mode.name(), 1);
}
});
return values;
}));
}
/**
* Sends the enabled addons (except GameModeAddons) of this server as bar chart.
* @since 1.17.1

View File

@ -1,10 +1,15 @@
package world.bentobox.bentobox;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@ -19,8 +24,12 @@ import world.bentobox.bentobox.api.user.Notifier;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.DynmapHook;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.MultipaperHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
import world.bentobox.bentobox.hooks.MyWorldsHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook;
import world.bentobox.bentobox.listeners.BannedCommands;
@ -28,14 +37,16 @@ import world.bentobox.bentobox.listeners.BlockEndDragon;
import world.bentobox.bentobox.listeners.DeathListener;
import world.bentobox.bentobox.listeners.JoinLeaveListener;
import world.bentobox.bentobox.listeners.PanelListenerManager;
import world.bentobox.bentobox.listeners.PortalTeleportationListener;
import world.bentobox.bentobox.listeners.PrimaryIslandListener;
import world.bentobox.bentobox.listeners.SeedWorldMakerListener;
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
import world.bentobox.bentobox.listeners.teleports.EntityTeleportListener;
import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener;
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.IslandChunkDeletionManager;
import world.bentobox.bentobox.managers.IslandDeletionManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
@ -51,7 +62,7 @@ import world.bentobox.bentobox.versions.ServerCompatibility;
* Main BentoBox class
* @author tastybento, Poslovitch
*/
public class BentoBox extends JavaPlugin {
public class BentoBox extends JavaPlugin implements Listener {
private static BentoBox instance;
@ -65,12 +76,10 @@ public class BentoBox extends JavaPlugin {
private AddonsManager addonsManager;
private FlagsManager flagsManager;
private IslandWorldManager islandWorldManager;
private RanksManager ranksManager;
private BlueprintsManager blueprintsManager;
private HooksManager hooksManager;
private PlaceholdersManager placeholdersManager;
private IslandDeletionManager islandDeletionManager;
private IslandChunkDeletionManager islandChunkDeletionManager;
private WebManager webManager;
// Settings
@ -95,6 +104,8 @@ public class BentoBox extends JavaPlugin {
@Override
public void onEnable(){
setInstance(this);
if (!ServerCompatibility.getInstance().checkCompatibility().isCanLaunch()) {
// The server's most likely incompatible.
// Show a warning
@ -116,7 +127,6 @@ public class BentoBox extends JavaPlugin {
// Save the default config from config.yml
saveDefaultConfig();
setInstance(this);
// Load Flags
flagsManager = new FlagsManager(this);
@ -134,7 +144,6 @@ public class BentoBox extends JavaPlugin {
return;
}
islandsManager = new IslandsManager(this);
ranksManager = new RanksManager();
// Start head getter
headGetter = new HeadGetter(this);
@ -178,8 +187,14 @@ public class BentoBox extends JavaPlugin {
private void completeSetup(long loadTime) {
final long enableStart = System.currentTimeMillis();
hooksManager.registerHook(new MultipaperHook());
hooksManager.registerHook(new VaultHook());
// MythicMobs
hooksManager.registerHook(new MythicMobsHook());
hooksManager.registerHook(new PlaceholderAPIHook());
// Setup the Placeholders manager
placeholdersManager = new PlaceholdersManager(this);
@ -201,20 +216,6 @@ public class BentoBox extends JavaPlugin {
return;
}
// Save islands & players data every X minutes
Bukkit.getScheduler().runTaskTimer(instance, () -> {
if (!playersManager.isSaveTaskRunning()) {
playersManager.saveAll(true);
} else {
getLogger().warning("Tried to start a player data save task while the previous auto save was still running!");
}
if (!islandsManager.isSaveTaskRunning()) {
islandsManager.saveAll(true);
} else {
getLogger().warning("Tried to start a island data save task while the previous auto save was still running!");
}
}, getSettings().getDatabaseBackupPeriod() * 20 * 60L, getSettings().getDatabaseBackupPeriod() * 20 * 60L);
// Make sure all flag listeners are registered.
flagsManager.registerListeners();
@ -225,10 +226,17 @@ public class BentoBox extends JavaPlugin {
// Register Multiverse hook - MV loads AFTER BentoBox
// Make sure all worlds are already registered to Multiverse.
hooksManager.registerHook(new MultiverseCoreHook());
islandWorldManager.registerWorldsToMultiverse();
hooksManager.registerHook(new MyWorldsHook());
islandWorldManager.registerWorldsToMultiverse(true);
// Register additional hooks
hooksManager.registerHook(new DynmapHook());
// Register Slimefun
hooksManager.registerHook(new SlimefunHook());
// Register ItemsAdder
hooksManager.registerHook(new ItemsAdderHook(this));
// TODO: re-enable after implementation
//hooksManager.registerHook(new DynmapHook());
// TODO: re-enable after rework
//hooksManager.registerHook(new LangUtilsHook());
@ -248,6 +256,8 @@ public class BentoBox extends JavaPlugin {
// Tell all addons that everything is loaded
isLoaded = true;
this.addonsManager.allLoaded();
// Run ready commands
settings.getReadyCommands().forEach(cmd -> Bukkit.getServer().dispatchCommand(getServer().getConsoleSender(), cmd));
// Fire plugin ready event - this should go last after everything else
Bukkit.getPluginManager().callEvent(new BentoBoxReadyEvent());
instance.log("All blueprints loaded.");
@ -288,18 +298,25 @@ public class BentoBox extends JavaPlugin {
manager.registerEvents(new PanelListenerManager(), this);
// Standard Nether/End spawns protection
manager.registerEvents(new StandardSpawnProtectionListener(this), this);
// Nether portals
manager.registerEvents(new PortalTeleportationListener(this), this);
// Player portals
manager.registerEvents(new PlayerTeleportListener(this), this);
// Entity portals
manager.registerEvents(new EntityTeleportListener(this), this);
// End dragon blocking
manager.registerEvents(new BlockEndDragon(this), this);
// Banned visitor commands
manager.registerEvents(new BannedCommands(this), this);
// Death counter
manager.registerEvents(new DeathListener(this), this);
// MV unregister
manager.registerEvents(this, this);
// Island Delete Manager
islandChunkDeletionManager = new IslandChunkDeletionManager(this);
islandDeletionManager = new IslandDeletionManager(this);
manager.registerEvents(islandDeletionManager, this);
// Primary Island Listener
manager.registerEvents(new PrimaryIslandListener(this), this);
// Seed world chunk generator
manager.registerEvents(new SeedWorldMakerListener(this), this);
}
@Override
@ -317,6 +334,19 @@ public class BentoBox extends JavaPlugin {
if (islandsManager != null) {
islandsManager.shutdown();
}
}
@EventHandler
public void onServerStop(ServerCommandEvent e) {
/* This is no longer needed as with https://github.com/Multiverse/Multiverse-Core/releases/tag/4.3.12 (or maybe earlier) the issue
* is fixed where the generator was not remembered across reboots.
*/
/*
if (islandWorldManager != null && (e.getCommand().equalsIgnoreCase("stop") || e.getCommand().equalsIgnoreCase("restart"))) {
// Unregister any MV worlds if () {
//islandWorldManager.registerWorldsToMultiverse(false);
}*/
}
/**
@ -395,9 +425,11 @@ public class BentoBox extends JavaPlugin {
/**
* @return the ranksManager
* @deprecated Just use {@code RanksManager.getInstance()}
*/
@Deprecated(since = "2.0.0", forRemoval = true)
public RanksManager getRanksManager() {
return ranksManager;
return RanksManager.getInstance();
}
/**
@ -431,6 +463,17 @@ public class BentoBox extends JavaPlugin {
getPluginLoader().disablePlugin(this);
return false;
}
log("Saving default panels...");
if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "island_creation_panel.yml"))) {
log("Saving default island_creation_panel...");
this.saveResource("panels/island_creation_panel.yml", false);
}
if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "language_panel.yml"))) {
log("Saving default language_panel...");
this.saveResource("panels/language_panel.yml", false);
}
return true;
}
@ -527,13 +570,6 @@ public class BentoBox extends JavaPlugin {
return islandDeletionManager;
}
/**
* @return the islandChunkDeletionManager
*/
public IslandChunkDeletionManager getIslandChunkDeletionManager() {
return islandChunkDeletionManager;
}
/**
* @return an optional of the Bstats instance
* @since 1.1

View File

@ -1,10 +1,18 @@
package world.bentobox.bentobox;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.bukkit.Material;
import com.google.common.collect.ImmutableList;
import world.bentobox.bentobox.api.configuration.ConfigComment;
import world.bentobox.bentobox.api.configuration.ConfigEntry;
import world.bentobox.bentobox.api.configuration.ConfigObject;
@ -13,34 +21,15 @@ import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
/**
* All the plugin settings are here
*
* @author tastybento
*/
@StoreAt(filename="config.yml") // Explicitly call out what name this should have.
@StoreAt(filename = "config.yml") // Explicitly call out what name this should have.
@ConfigComment("BentoBox v[version] configuration file.")
@ConfigComment("")
@ConfigComment("This configuration file contains settings that mainly apply to or manage the following elements:")
@ConfigComment(" * Data storage")
@ConfigComment(" * Gamemodes (commands, ...)")
@ConfigComment(" * Internet connectivity (web-based content-enriched features, ...)")
@ConfigComment("")
@ConfigComment("Note that this configuration file is dynamic:")
@ConfigComment(" * It gets updated with the newest settings and comments after BentoBox loaded its settings from it.")
@ConfigComment(" * Upon updating BentoBox, new settings will be automatically added into this configuration file.")
@ConfigComment(" * Said settings are distinguishable by a dedicated comment, which looks like this:")
@ConfigComment(" Added since X.Y.Z.")
@ConfigComment(" * They are provided with default values that should not cause issues on live production servers.")
@ConfigComment(" * You can however edit this file while the server is online.")
@ConfigComment(" You will therefore need to run the following command in order to take the changes into account: /bentobox reload.")
@ConfigComment("")
@ConfigComment("Here are a few pieces of advice before you get started:")
@ConfigComment(" * You should check out our Wiki, which may provide you useful tips or insights about BentoBox's features.")
@ConfigComment(" Link: https://github.com/BentoBoxWorld/BentoBox/wiki")
@ConfigComment(" * You should edit this configuration file while the server is offline.")
@ConfigComment(" * Moreover, whenever you update BentoBox, you should do so on a test server first.")
@ConfigComment(" This will allow you to configure the new settings beforehand instead of applying them inadvertently on a live production server.")
public class Settings implements ConfigObject {
/* GENERAL */
/* GENERAL */
@ConfigComment("Default language for new players.")
@ConfigComment("This is the filename in the locale folder without .yml.")
@ConfigComment("If this does not exist, the default en-US will be used.")
@ -52,10 +41,17 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.use-economy")
private boolean useEconomy = true;
/* COMMANDS */
@ConfigComment("Console commands to run when BentoBox has loaded all worlds and addons.")
@ConfigComment("Commands are run as the console.")
@ConfigComment("e.g. set aliases for worlds in Multiverse here, or anything you need to")
@ConfigComment("run after the plugin is fully loaded.")
@ConfigEntry(path = "general.ready-commands", since = "1.24.2")
private List<String> readyCommands = new ArrayList<>();
// Database
@ConfigComment("JSON, MYSQL, MARIADB, MONGODB, SQLITE, POSTGRESQL and YAML(deprecated).")
@ConfigComment("JSON, MYSQL, MARIADB, MONGODB, SQLITE, and POSTGRESQL.")
@ConfigComment("Transition database options are:")
@ConfigComment(" YAML2JSON, YAML2MARIADB, YAML2MYSQL, YAML2MONGODB, YAML2SQLITE")
@ConfigComment(" JSON2MARIADB, JSON2MYSQL, JSON2MONGODB, JSON2SQLITE, JSON2POSTGRESQL")
@ConfigComment(" MYSQL2JSON, MARIADB2JSON, MONGODB2JSON, SQLITE2JSON, POSTGRESQL2JSON")
@ConfigComment("If you need others, please make a feature request.")
@ -66,8 +62,9 @@ public class Settings implements ConfigObject {
@ConfigComment(" SQLite versions 3.28 or later")
@ConfigComment(" PostgreSQL versions 9.4 or later")
@ConfigComment("Transition options enable migration from one database type to another. Use /bbox migrate.")
@ConfigComment("YAML and JSON are file-based databases.")
@ConfigComment("JSON is a file-based database.")
@ConfigComment("MYSQL might not work with all implementations: if available, use a dedicated database type (e.g. MARIADB).")
@ConfigComment("BentoBox uses HikariCP for connecting with SQL databases.")
@ConfigComment("If you use MONGODB, you must also run the BSBMongo plugin (not addon).")
@ConfigComment("See https://github.com/tastybento/bsbMongo/releases/.")
@ConfigEntry(path = "general.database.type", video = "https://youtu.be/FFzCk5-y7-g")
@ -107,6 +104,11 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.database.max-saved-islands-per-tick")
private int maxSavedIslandsPerTick = 20;
@ConfigComment("Number of active connections to the SQL database at the same time.")
@ConfigComment("Default 10.")
@ConfigEntry(path = "general.database.max-pool-size", since = "1.21.0")
private int maximumPoolSize = 10;
@ConfigComment("Enable SSL connection to MongoDB, MariaDB, MySQL and PostgreSQL databases.")
@ConfigEntry(path = "general.database.use-ssl", since = "1.12.0")
private boolean useSSL = false;
@ -118,6 +120,16 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.database.prefix-character", since = "1.13.0")
private String databasePrefix = "";
@ConfigComment("Custom connection datasource properties that will be applied to connection pool.")
@ConfigComment("Check available values to your SQL driver implementation.")
@ConfigComment("Example: ")
@ConfigComment(" custom-properties: ")
@ConfigComment(" cachePrepStmts: 'true'")
@ConfigComment(" prepStmtCacheSize: '250'")
@ConfigComment(" prepStmtCacheSqlLimit: '2048'")
@ConfigEntry(path = "general.database.custom-properties", since = "1.21.0")
private Map<String, String> customPoolProperties = new HashMap<>();
@ConfigComment("MongoDB client connection URI to override default connection options.")
@ConfigComment("See: https://docs.mongodb.com/manual/reference/connection-string/")
@ConfigEntry(path = "general.database.mongodb-connection-uri", since = "1.14.0")
@ -177,6 +189,18 @@ public class Settings implements ConfigObject {
/*
* Island
*/
// Number of islands
@ConfigComment("The default number of concurrent islands a player may have.")
@ConfigComment("This may be overridden by individual game mode config settings.")
@ConfigEntry(path = "island.concurrent-islands")
private int islandNumber = 1;
@ConfigComment("Hide used blueprints.")
@ConfigComment("Blueprints can have a maximum use when players have concurrent islands.")
@ConfigComment("If this is true, then ones that are used up will not be shown in the island create menu.")
@ConfigEntry(path = "island.hide-used-blueprints", since = "2.3.0")
private boolean hideUsedBlueprints = false;
// Cooldowns
@ConfigComment("How long a player must wait until they can rejoin a team island after being kicked in minutes.")
@ConfigComment("This slows the effectiveness of players repeating challenges")
@ -267,25 +291,6 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.delete-speed", since = "1.7.0")
private int deleteSpeed = 1;
// Automated ownership transfer
@ConfigComment("Toggles the automated ownership transfer.")
@ConfigComment("It automatically transfers the ownership of an island to one of its members in case the current owner is inactive.")
@ConfigComment("More precisely, it transfers the ownership of the island to the player who's active, whose rank is the highest")
@ConfigComment("and who's been part of the island the longest time.")
@ConfigComment("Setting this to 'false' will disable the feature.")
@ConfigEntry(path = "island.automated-ownership-transfer.enable", hidden = true)
private boolean enableAutoOwnershipTransfer = false;
@ConfigComment("Time in days since the island owner's last disconnection before they are considered inactive.")
@ConfigEntry(path = "island.automated-ownership-transfer.inactivity-threshold", hidden = true)
private int autoOwnershipTransferInactivityThreshold = 30;
@ConfigComment("Ranks are being considered when transferring the island ownership to one of its member.")
@ConfigComment("Ignoring ranks will result in the island ownership being transferred to the player who's active and")
@ConfigComment("who's been member of the island the longest time.")
@ConfigEntry(path = "island.automated-ownership-transfer.ignore-ranks", hidden = true)
private boolean autoOwnershipTransferIgnoreRanks = false;
// Island deletion related settings
@ConfigComment("Toggles whether islands, when players are resetting them, should be kept in the world or deleted.")
@ConfigComment("* If set to 'true', whenever a player resets his island, his previous island will become unowned and won't be deleted from the world.")
@ -314,6 +319,12 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.safe-spot-search-vertical-range", since = "1.19.1")
private int safeSpotSearchVerticalRange = 400;
@ConfigComment("By default, if the destination is not safe, the plugin will try to search for a safe spot around the destination.")
@ConfigComment("This allows to change the distance for searching this spot. Larger value will mean longer position search.")
@ConfigComment("This value is also used for valid nether portal linking between dimension.")
@ConfigEntry(path = "island.safe-spot-search-range", since = "1.21.0")
private int safeSpotSearchRange = 16;
/* WEB */
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
@ConfigComment("Disabling this will result in the deactivation of the update checker and of some other")
@ -376,6 +387,7 @@ public class Settings implements ConfigObject {
/**
* This method returns the useSSL value.
*
* @return the value of useSSL.
* @since 1.12.0
*/
@ -385,6 +397,7 @@ public class Settings implements ConfigObject {
/**
* This method sets the useSSL value.
*
* @param useSSL the useSSL new value.
* @since 1.12.0
*/
@ -604,30 +617,6 @@ public class Settings implements ConfigObject {
this.deleteSpeed = deleteSpeed;
}
public boolean isEnableAutoOwnershipTransfer() {
return enableAutoOwnershipTransfer;
}
public void setEnableAutoOwnershipTransfer(boolean enableAutoOwnershipTransfer) {
this.enableAutoOwnershipTransfer = enableAutoOwnershipTransfer;
}
public int getAutoOwnershipTransferInactivityThreshold() {
return autoOwnershipTransferInactivityThreshold;
}
public void setAutoOwnershipTransferInactivityThreshold(int autoOwnershipTransferInactivityThreshold) {
this.autoOwnershipTransferInactivityThreshold = autoOwnershipTransferInactivityThreshold;
}
public boolean isAutoOwnershipTransferIgnoreRanks() {
return autoOwnershipTransferIgnoreRanks;
}
public void setAutoOwnershipTransferIgnoreRanks(boolean autoOwnershipTransferIgnoreRanks) {
this.autoOwnershipTransferIgnoreRanks = autoOwnershipTransferIgnoreRanks;
}
public boolean isLogCleanSuperFlatChunks() {
return logCleanSuperFlatChunks;
}
@ -699,7 +688,8 @@ public class Settings implements ConfigObject {
* @return the clearRadius
*/
public int getClearRadius() {
if (clearRadius < 0) clearRadius = 0;
if (clearRadius < 0)
clearRadius = 0;
return clearRadius;
}
@ -707,7 +697,8 @@ public class Settings implements ConfigObject {
* @param clearRadius the clearRadius to set. Cannot be negative.
*/
public void setClearRadius(int clearRadius) {
if (clearRadius < 0) clearRadius = 0;
if (clearRadius < 0)
clearRadius = 0;
this.clearRadius = clearRadius;
}
@ -731,7 +722,8 @@ public class Settings implements ConfigObject {
* @return the databasePrefix
*/
public String getDatabasePrefix() {
if (databasePrefix == null) databasePrefix = "";
if (databasePrefix == null)
databasePrefix = "";
return databasePrefix.isEmpty() ? "" : databasePrefix.replaceAll("[^a-zA-Z0-9]", "_");
}
@ -744,7 +736,9 @@ public class Settings implements ConfigObject {
/**
* Returns whether islands, when reset, should be kept or deleted.
* @return {@code true} if islands, when reset, should be kept; {@code false} otherwise.
*
* @return {@code true} if islands, when reset, should be kept; {@code false}
* otherwise.
* @since 1.13.0
*/
public boolean isKeepPreviousIslandOnReset() {
@ -753,7 +747,9 @@ public class Settings implements ConfigObject {
/**
* Sets whether islands, when reset, should be kept or deleted.
* @param keepPreviousIslandOnReset {@code true} if islands, when reset, should be kept; {@code false} otherwise.
*
* @param keepPreviousIslandOnReset {@code true} if islands, when reset, should
* be kept; {@code false} otherwise.
* @since 1.13.0
*/
public void setKeepPreviousIslandOnReset(boolean keepPreviousIslandOnReset) {
@ -761,10 +757,13 @@ public class Settings implements ConfigObject {
}
/**
* Returns a MongoDB client connection URI to override default connection options.
* Returns a MongoDB client connection URI to override default connection
* options.
*
* @return mongodb client connection.
* @see <a href="https://docs.mongodb.com/manual/reference/connection-string/">MongoDB Documentation</a>
* @see <a href=
* "https://docs.mongodb.com/manual/reference/connection-string/">MongoDB
* Documentation</a>
* @since 1.14.0
*/
public String getMongodbConnectionUri() {
@ -773,6 +772,7 @@ public class Settings implements ConfigObject {
/**
* Set the MongoDB client connection URI.
*
* @param mongodbConnectionUri connection URI.
* @since 1.14.0
*/
@ -781,8 +781,11 @@ public class Settings implements ConfigObject {
}
/**
* Returns the Material of the item to preferably use when one needs to fill gaps in Panels.
* @return the Material of the item to preferably use when one needs to fill gaps in Panels.
* Returns the Material of the item to preferably use when one needs to fill
* gaps in Panels.
*
* @return the Material of the item to preferably use when one needs to fill
* gaps in Panels.
* @since 1.14.0
*/
public Material getPanelFillerMaterial() {
@ -790,106 +793,96 @@ public class Settings implements ConfigObject {
}
/**
* Sets the Material of the item to preferably use when one needs to fill gaps in Panels.
* @param panelFillerMaterial the Material of the item to preferably use when one needs to fill gaps in Panels.
* Sets the Material of the item to preferably use when one needs to fill gaps
* in Panels.
*
* @param panelFillerMaterial the Material of the item to preferably use when
* one needs to fill gaps in Panels.
* @since 1.14.0
*/
public void setPanelFillerMaterial(Material panelFillerMaterial) {
this.panelFillerMaterial = panelFillerMaterial;
}
/**
* Method Settings#getPlayerHeadCacheTime returns the playerHeadCacheTime of this object.
* Method Settings#getPlayerHeadCacheTime returns the playerHeadCacheTime of
* this object.
*
* @return the playerHeadCacheTime (type long) of this object.
* @since 1.14.1
*/
public long getPlayerHeadCacheTime()
{
public long getPlayerHeadCacheTime() {
return playerHeadCacheTime;
}
/**
* Method Settings#setPlayerHeadCacheTime sets new value for the playerHeadCacheTime of this object.
* Method Settings#setPlayerHeadCacheTime sets new value for the
* playerHeadCacheTime of this object.
*
* @param playerHeadCacheTime new value for this object.
* @since 1.14.1
*/
public void setPlayerHeadCacheTime(long playerHeadCacheTime)
{
public void setPlayerHeadCacheTime(long playerHeadCacheTime) {
this.playerHeadCacheTime = playerHeadCacheTime;
}
/**
* Is use cache server boolean.
*
* @return the boolean
* @since 1.16.0
*/
public boolean isUseCacheServer()
{
public boolean isUseCacheServer() {
return useCacheServer;
}
/**
* Sets use cache server.
*
* @param useCacheServer the use cache server
* @since 1.16.0
*/
public void setUseCacheServer(boolean useCacheServer)
{
public void setUseCacheServer(boolean useCacheServer) {
this.useCacheServer = useCacheServer;
}
/**
* Gets heads per call.
*
* @return the heads per call
* @since 1.16.0
*/
public int getHeadsPerCall()
{
public int getHeadsPerCall() {
return headsPerCall;
}
/**
* Sets heads per call.
*
* @param headsPerCall the heads per call
* @since 1.16.0
*/
public void setHeadsPerCall(int headsPerCall)
{
public void setHeadsPerCall(int headsPerCall) {
this.headsPerCall = headsPerCall;
}
/**
* Gets ticks between calls.
*
* @return the ticks between calls
* @since 1.16.0
*/
public long getTicksBetweenCalls()
{
public long getTicksBetweenCalls() {
return ticksBetweenCalls;
}
/**
* Sets ticks between calls.
*
* @param ticksBetweenCalls the ticks between calls
* @since 1.16.0
*/
public void setTicksBetweenCalls(long ticksBetweenCalls)
{
public void setTicksBetweenCalls(long ticksBetweenCalls) {
this.ticksBetweenCalls = ticksBetweenCalls;
}
@ -907,19 +900,138 @@ public class Settings implements ConfigObject {
this.minPortalSearchRadius = minPortalSearchRadius;
}
/**
* Gets safe spot search vertical range.
*
* @return the safe spot search vertical range
*/
public int getSafeSpotSearchVerticalRange() {
return safeSpotSearchVerticalRange;
}
/**
* Sets safe spot search vertical range.
*
* @param safeSpotSearchVerticalRange the safe spot search vertical range
*/
public void setSafeSpotSearchVerticalRange(int safeSpotSearchVerticalRange) {
this.safeSpotSearchVerticalRange = safeSpotSearchVerticalRange;
}
/**
* Is slow deletion boolean.
*
* @return the boolean
*/
public boolean isSlowDeletion() {
return slowDeletion;
}
/**
* Sets slow deletion.
*
* @param slowDeletion the slow deletion
*/
public void setSlowDeletion(boolean slowDeletion) {
this.slowDeletion = slowDeletion;
}
/**
* Gets maximum pool size.
*
* @return the maximum pool size
*/
public int getMaximumPoolSize() {
return maximumPoolSize;
}
/**
* Gets safe spot search range.
*
* @return the safe spot search range
*/
public int getSafeSpotSearchRange() {
return safeSpotSearchRange;
}
/**
* Sets maximum pool size.
*
* @param maximumPoolSize the maximum pool size
*/
public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
/**
* Gets custom pool properties.
*
* @return the custom pool properties
*/
public Map<String, String> getCustomPoolProperties() {
return customPoolProperties;
}
/**
* Sets custom pool properties.
*
* @param customPoolProperties the custom pool properties
*/
public void setCustomPoolProperties(Map<String, String> customPoolProperties) {
this.customPoolProperties = customPoolProperties;
}
/**
* Sets safe spot search range.
*
* @param safeSpotSearchRange the safe spot search range
*/
public void setSafeSpotSearchRange(int safeSpotSearchRange) {
this.safeSpotSearchRange = safeSpotSearchRange;
}
/**
* @return an immutable list of readyCommands
*/
public List<String> getReadyCommands() {
return ImmutableList.copyOf(Objects.requireNonNullElse(readyCommands, Collections.emptyList()));
}
/**
* @param readyCommands the readyCommands to set
*/
public void setReadyCommands(List<String> readyCommands) {
this.readyCommands = readyCommands;
}
/**
* @return the islandNumber
* @since 2.0.0
*/
public int getIslandNumber() {
return islandNumber;
}
/**
* @param islandNumber the islandNumber to set
* @since 2.0.0
*/
public void setIslandNumber(int islandNumber) {
this.islandNumber = islandNumber;
}
/**
* @return the hideUsedBlueprints
*/
public boolean isHideUsedBlueprints() {
return hideUsedBlueprints;
}
/**
* @param hideUsedBlueprints the hideUsedBlueprints to set
*/
public void setHideUsedBlueprints(boolean hideUsedBlueprints) {
this.hideUsedBlueprints = hideUsedBlueprints;
}
}

View File

@ -12,6 +12,7 @@ import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.bukkit.Bukkit;
import org.bukkit.Server;
@ -20,6 +21,8 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Listener;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.request.AddonRequestHandler;
import world.bentobox.bentobox.api.flags.Flag;
@ -44,6 +47,8 @@ public abstract class Addon {
protected Addon() {
state = State.DISABLED;
// If the config is updated, update the config.
MultiLib.onString(getPlugin(), "bentobox-config-update", v -> this.reloadConfig());
}
/**
@ -273,7 +278,9 @@ public abstract class Addon {
"The embedded resource '" + jarResource + "' cannot be found in " + jar.getName());
}
// There are two options, use the path of the resource or not
File outFile = new File(destinationFolder, jarResource);
File outFile = new File(destinationFolder,
jarResource.replaceAll("/", Matcher.quoteReplacement(File.separator)));
if (noPath) {
outFile = new File(destinationFolder, outFile.getName());
}

View File

@ -7,6 +7,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -33,6 +34,19 @@ public class AddonClassLoader extends URLClassLoader {
private final Addon addon;
private final AddonsManager loader;
/**
* For testing only
* @param addon addon
* @param loader Addons Manager
* @param jarFile Jar File
* @throws MalformedURLException exception
*/
protected AddonClassLoader(Addon addon, AddonsManager loader, File jarFile) throws MalformedURLException {
super(new URL[]{jarFile.toURI().toURL()});
this.addon = addon;
this.loader = loader;
}
public AddonClassLoader(AddonsManager addonsManager, YamlConfiguration data, File jarFile, ClassLoader parent)
throws InvalidAddonInheritException,
MalformedURLException,
@ -79,8 +93,27 @@ public class AddonClassLoader extends URLClassLoader {
*/
@NonNull
public static AddonDescription asDescription(YamlConfiguration data) throws InvalidAddonDescriptionException {
AddonDescription.Builder builder = new AddonDescription.Builder(Objects.requireNonNull(data.getString("main")), Objects.requireNonNull(data.getString("name")), Objects.requireNonNull(data.getString("version")))
// Validate addon.yml
if (!data.contains("main")) {
throw new InvalidAddonDescriptionException("Missing 'main' tag. A main class must be listed in addon.yml");
}
if (!data.contains("name")) {
throw new InvalidAddonDescriptionException("Missing 'name' tag. An addon name must be listed in addon.yml");
}
if (!data.contains("version")) {
throw new InvalidAddonDescriptionException("Missing 'version' tag. A version must be listed in addon.yml");
}
if (!data.contains("authors")) {
throw new InvalidAddonDescriptionException("Missing 'authors' tag. At least one author must be listed in addon.yml");
}
AddonDescription.Builder builder = new AddonDescription.Builder(
// Mandatory elements
Objects.requireNonNull(data.getString("main")),
Objects.requireNonNull(data.getString("name")),
Objects.requireNonNull(data.getString("version")))
.authors(Objects.requireNonNull(data.getString("authors")))
// Optional elements
.metrics(data.getBoolean("metrics", true))
.repository(data.getString("repository", ""));
@ -92,7 +125,11 @@ public class AddonClassLoader extends URLClassLoader {
if (softDepend != null) {
builder.softDependencies(Arrays.asList(softDepend.split("\\s*,\\s*")));
}
builder.icon(Objects.requireNonNull(Material.getMaterial(data.getString("icon", "PAPER"))));
Material icon = Material.getMaterial(data.getString("icon", "PAPER").toUpperCase(Locale.ENGLISH));
if (icon == null) {
throw new InvalidAddonDescriptionException("'icon' tag refers to an unknown Material: " + data.getString("icon"));
}
builder.icon(Objects.requireNonNull(icon));
String apiVersion = data.getString("api-version");
if (apiVersion != null) {

View File

@ -287,7 +287,7 @@ public final class AddonDescription {
*/
@Override
public String toString() {
return "AddonDescription [" + (name != null ? "name=" + name + ", " : "")
return "AddonDescription [" + "name=" + name + ", "
+ "version=" + version + "]";
}
}

View File

@ -8,6 +8,8 @@ import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.util.Util;
@ -117,7 +119,7 @@ public abstract class GameModeAddon extends Addon {
/**
* Defines the world generator for this game mode
* @param worldName - name of world that this applies to
* @param id - id if any
* @param id - id if any. "delete" is used when this request is for island deletion purposes
* @return Chunk generator or null if one does not exist, e.g. the use own generator setting is true
* @since 1.2.0
*/
@ -129,6 +131,18 @@ public abstract class GameModeAddon extends Addon {
* in-game and need to be saved.
* @since 1.4.0
*/
public abstract void saveWorldSettings();
public void saveWorldSettings() {
// Inform other servers
MultiLib.notify("bentobox-config-update", "");
}
/**
* Defines if the game mode uses the latest {@link ChunkGenerator} API or
* deprecated {@link ChunkGenerator#generateChunkData(World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)} approach.
* @return true if this game mode is a void world and should just be deleted as such
*/
public boolean isUsesNewChunkGeneration() {
return false;
}
}

View File

@ -15,7 +15,7 @@ import com.google.common.io.Files;
*/
public abstract class Pladdon extends JavaPlugin {
private static final String ADDONS_FOLDER = "BentoBox/addons";
private static final String ADDONS_FOLDER = "BentoBox" + File.separator + "addons";
/**
* This must return a new instance of the addon. It is called when the Pladdon is loaded.
@ -28,12 +28,12 @@ public abstract class Pladdon extends JavaPlugin {
String parentFolder = getFile().getParent();
if (parentFolder == null || !parentFolder.endsWith(ADDONS_FOLDER)) {
// Jar is in the wrong place. Let's move it
moveJar();
//moveJar();
}
}
protected void moveJar() {
getLogger().severe(getFile().getName() + " must be in the BentoBox/addons folder! Trying to move it there...");
getLogger().severe(getFile().getName() + " must be in the " + ADDONS_FOLDER + " folder! Trying to move it there...");
File addons = new File(getFile().getParent(), ADDONS_FOLDER);
if (addons.exists() || addons.mkdirs()) {
File to = new File(addons, getFile().getName());
@ -44,7 +44,7 @@ public abstract class Pladdon extends JavaPlugin {
} catch (IOException ex) {
getLogger().severe("Failed to move it. " + ex.getMessage());
getLogger().severe("Move " + getFile().getName() + " manually into the BentoBox/addons folder. Then restart server.");
getLogger().severe("Move " + getFile().getName() + " manually into the " + ADDONS_FOLDER + " folder. Then restart server.");
}
} else {
getLogger().warning(getFile().getName() + " already is in the addons folder. Delete the one in the plugins folder.");

View File

@ -2,12 +2,11 @@ package world.bentobox.bentobox.api.addons.exceptions;
import java.io.Serial;
public class AddonRequestException extends AddonException
{
@Serial
public class AddonRequestException extends AddonException {
@Serial
private static final long serialVersionUID = -5698456013070166174L;
public AddonRequestException(String errorMessage) {
super(errorMessage);
}
public AddonRequestException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -3,7 +3,7 @@
* <p>
* Addons can expose data that they want to expose. To access it, call this class with the appropriate addon name,
* the label for the data that is requested and if required, a map of key-value pairs that will be given to the addon.
*
* <p>
* <b>Note</b> Since BentoBox 1.17.0, Addons can be declared as Pladdons and be loaded by the Bukkit classloader. This
* enables Plugins to access Addon methods directly so this API is not required.
* </p>

View File

@ -11,7 +11,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.bukkit.World;
import org.bukkit.command.Command;
@ -27,6 +26,7 @@ import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.events.command.CommandEvent;
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.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
@ -35,6 +35,7 @@ import world.bentobox.bentobox.util.Util;
/**
* BentoBox composite command. Provides an abstract implementation of a command.
*
* @author tastybento
* @author Poslovitch
*/
@ -49,6 +50,13 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
*/
private boolean onlyPlayer = false;
/**
* True if the command is only for the console
*
* @since 1.24.0
*/
private boolean onlyConsole = false;
/**
* True if command is a configurable rank
*/
@ -56,22 +64,26 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Make default command rank as owner rank.
*
* @since 1.20.0
*/
private int defaultCommandRank = RanksManager.OWNER_RANK;
/**
* True if command is hidden from help and tab complete
*
* @since 1.13.0
*/
private boolean hidden = false;
/**
* The parameters string for this command. It is the commands followed by a locale reference.
* The parameters string for this command. It is the commands followed by a
* locale reference.
*/
private String parameters = "";
/**
* The parent command to this one. If this is a top-level command it will be empty.
* The parent command to this one. If this is a top-level command it will be
* empty.
*/
protected final CompositeCommand parent;
/**
@ -103,8 +115,9 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
private final String permissionPrefix;
/**
* The world that this command operates in. This is an overworld and will cover any associated nether or end
* If the world value does not exist, then the command is general across worlds
* The world that this command operates in. This is an overworld and will cover
* any associated nether or end If the world value does not exist, then the
* command is general across worlds
*/
private World world;
@ -125,8 +138,9 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Top level command
* @param addon - addon creating the command
* @param label - string for this command
*
* @param addon - addon creating the command
* @param label - string for this command
* @param aliases - aliases
*/
protected CompositeCommand(Addon addon, String label, String... aliases) {
@ -158,17 +172,19 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* This is the top-level command constructor for commands that have no parent.
* @param label - string for this command
*
* @param label - string for this command
* @param aliases - aliases for this command
*/
protected CompositeCommand(String label, String... aliases) {
this((Addon)null, label, aliases);
this((Addon) null, label, aliases);
}
/**
* Sub-command constructor
* @param parent - the parent composite command
* @param label - string label for this subcommand
*
* @param parent - the parent composite command
* @param label - string label for this subcommand
* @param aliases - aliases for this subcommand
*/
protected CompositeCommand(CompositeCommand parent, String label, String... aliases) {
@ -176,12 +192,14 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Command to register a command from an addon under a parent command (that could be from another addon)
* @param addon - this command's addon
* @param parent - parent command
* Command to register a command from an addon under a parent command (that
* could be from another addon)
*
* @param addon - this command's addon
* @param parent - parent command
* @param aliases - aliases for this command
*/
protected CompositeCommand(Addon addon, CompositeCommand parent, String label, String... aliases ) {
protected CompositeCommand(Addon addon, CompositeCommand parent, String label, String... aliases) {
super(label, "", "", Arrays.asList(aliases));
this.topLabel = parent.getTopLabel();
this.plugin = BentoBox.getInstance();
@ -214,7 +232,8 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
setDescription(COMMANDS + reference + ".description");
setParametersHelp(COMMANDS + reference + ".parameters");
setup();
// If this command does not define its own help class, then use the default help command
// If this command does not define its own help class, then use the default help
// command
if (getSubCommand("help").isEmpty() && !label.equals("help")) {
new DefaultHelpCommand(this);
}
@ -229,42 +248,42 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
// Get the User instance for this sender
User user = User.getInstance(sender);
// Fire an event to see if this command should be cancelled
CommandEvent event = CommandEvent.builder()
.setCommand(this)
.setLabel(label)
.setSender(sender)
.setArgs(args)
CommandEvent event = CommandEvent.builder().setCommand(this).setLabel(label).setSender(sender).setArgs(args)
.build();
if (event.isCancelled()) {
return false;
}
// Get command
CompositeCommand cmd = getCommandFromArgs(args);
String cmdLabel = (cmd.subCommandLevel > 0) ? args[cmd.subCommandLevel-1] : label;
String cmdLabel = (cmd.subCommandLevel > 0) ? args[cmd.subCommandLevel - 1] : label;
List<String> cmdArgs = Arrays.asList(args).subList(cmd.subCommandLevel, args.length);
// Call
return cmd.call(user, cmdLabel, cmdArgs);
}
/**
* Calls this composite command.
* Does not traverse the tree of subcommands in args.
* Event is not fired and it cannot be cancelled.
* @param user - user calling this command
* Calls this composite command. Does not traverse the tree of subcommands in
* args. Event is not fired and it cannot be cancelled.
*
* @param user - user calling this command
* @param cmdLabel - label used
* @param cmdArgs - list of args
* @param cmdArgs - list of args
* @return {@code true} if successful, {@code false} if not.
* @since 1.5.3
*/
public boolean call(User user, String cmdLabel, List<String> cmdArgs) {
// Check for console and permissions
if (onlyPlayer && !user.isPlayer()) {
if (isOnlyPlayer() && !user.isPlayer()) {
user.sendMessage("general.errors.use-in-game");
return false;
}
// Check perms, but only if this isn't the console
if (user.isPlayer() && !user.isOp() && getPermission() != null && !getPermission().isEmpty() && !user.hasPermission(getPermission())) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, getPermission());
if (isOnlyConsole() && user.isPlayer()) {
user.sendMessage("general.errors.use-in-console");
return false;
}
if (!this.runPermissionCheck(user)) {
// Error message is displayed by permission check.
return false;
}
// Set the user's addon context
@ -273,8 +292,29 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return canExecute(user, cmdLabel, cmdArgs) && execute(user, cmdLabel, cmdArgs);
}
/**
* This method checks and returns if user has access to the called command. It
* also recursively checks if user has access to the all parent commands.
*
* @param user User who permission must be checked.
* @return {@code true} is user can execute given command, {@code false}
* otherwise.
*/
private boolean runPermissionCheck(User user) {
// Check perms, but only if this isn't the console
if (user.isPlayer() && !user.isOp() && this.getPermission() != null && !this.getPermission().isEmpty()
&& !user.hasPermission(this.getPermission())) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, this.getPermission());
return false;
}
// Recursive permission check to find if user has access to the parent command.
return this.getParent() == null || this.getParent().runPermissionCheck(user);
}
/**
* Get the current composite command based on the arguments
*
* @param args - arguments
* @return the current composite command based on the arguments
*/
@ -303,6 +343,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Convenience method to get the island manager
*
* @return IslandsManager
*/
protected IslandsManager getIslands() {
@ -310,8 +351,17 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* @return this command's sub-level. Top level is 0.
* Every time a command registers with a parent, their level will be set.
* Convenience method to get the island manager
*
* @return IslandsManager
*/
protected IslandsManager getIslandsManager() {
return plugin.getIslandsManager();
}
/**
* @return this command's sub-level. Top level is 0. Every time a command
* registers with a parent, their level will be set.
*/
protected int getLevel() {
return subCommandLevel;
@ -325,13 +375,19 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Convenience method to obtain team members
* Convenience method to obtain team members of the active island for user. Note
* that the user may have more than one island in this world.
*
* @param world - world to check
* @param user - the User
* @return set of UUIDs of all team members
* @param user - the User
* @return set of UUIDs of all team members, or empty set if there is no island
*/
protected Set<UUID> getMembers(World world, User user) {
return plugin.getIslands().getMembers(world, user.getUniqueId());
Island island = plugin.getIslands().getIsland(world, user);
if (island == null) {
return Set.of();
}
return island.getMemberSet();
}
public String getParameters() {
@ -352,6 +408,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Convenience method to get the player manager
*
* @return PlayersManager
*/
protected PlayersManager getPlayers() {
@ -365,11 +422,13 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Get the island worlds manager
*
* @return island worlds manager
*/
public IslandWorldManager getIWM() {
return plugin.getIWM();
}
/**
* @return Settings object
*/
@ -379,6 +438,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Returns the CompositeCommand object referring to this command label
*
* @param label - command label or alias
* @return CompositeCommand or null if none found
*/
@ -402,9 +462,12 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Returns a map of sub commands for this command.
* As it needs more calculations to handle the Help subcommand, it is preferable to use {@link #getSubCommands()} when no such distinction is needed.
* @param ignoreHelp Whether the Help subcommand should not be returned in the map or not.
* Returns a map of sub commands for this command. As it needs more calculations
* to handle the Help subcommand, it is preferable to use
* {@link #getSubCommands()} when no such distinction is needed.
*
* @param ignoreHelp Whether the Help subcommand should not be returned in the
* map or not.
* @return Map of sub commands for this command
* @see #hasSubCommands(boolean)
*/
@ -417,17 +480,6 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return getSubCommands();
}
/**
* Convenience method to obtain the user's island owner
* @param world world to check
* @param user the User
* @return UUID of player's island owner or null if user has no island
*/
@Nullable
protected UUID getOwner(@NonNull World world, @NonNull User user) {
return plugin.getIslands().getOwner(world, user.getUniqueId());
}
@Override
public @NonNull String getUsage() {
return "/" + usage;
@ -435,6 +487,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if this command has a specific sub command.
*
* @param subCommand - sub command
* @return true if this command has this sub command
*/
@ -444,6 +497,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if this command has any sub commands.
*
* @return true if this command has subcommands
*/
protected boolean hasSubCommands() {
@ -451,9 +505,12 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Check if this command has any sub commands.
* As it needs more calculations to handle the Help subcommand, it is preferable to use {@link #hasSubCommands()} when no such distinction is needed.
* @param ignoreHelp Whether the Help subcommand should not be taken into account or not.
* Check if this command has any sub commands. As it needs more calculations to
* handle the Help subcommand, it is preferable to use {@link #hasSubCommands()}
* when no such distinction is needed.
*
* @param ignoreHelp Whether the Help subcommand should not be taken into
* account or not.
* @return true if this command has subcommands
* @see #getSubCommands(boolean)
*/
@ -463,8 +520,10 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Convenience method to check if a user has a team.
* Consider checking the island itself {@link Island#inTeam(UUID)}
*
* @param world - the world to check
* @param user - the User
* @param user - the User
* @return true if player is in a team
*/
protected boolean inTeam(World world, User user) {
@ -473,6 +532,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if this command is only for players.
*
* @return true or false
*/
public boolean isOnlyPlayer() {
@ -480,23 +540,23 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Convenience method to check if a user is a player
* @param user - the User
* @return true if sender is a player
* @deprecated use {@link User#isPlayer()}
* @forRemove 1.18.0
* Check if this command is only for consoles.
*
* @return true or false
*/
@Deprecated
protected boolean isPlayer(User user) {
return user.isPlayer();
public boolean isOnlyConsole() {
return onlyConsole;
}
/**
* Sets whether this command should only be run by players.
* If this is set to {@code true}, this command will only be runnable by objects implementing {@link Player}.
* <br/><br/>
* The default value provided when instantiating this CompositeCommand is {@code false}.
* Therefore, this method should only be used in case you want to explicitly edit the value.
* Sets whether this command should only be run by players. If this is set to
* {@code true}, this command will only be runnable by objects implementing
* {@link Player}. <br/>
* <br/>
* The default value provided when instantiating this CompositeCommand is
* {@code false}. Therefore, this method should only be used in case you want to
* explicitly edit the value.
*
* @param onlyPlayer {@code true} if this command should only be run by players.
*/
public void setOnlyPlayer(boolean onlyPlayer) {
@ -504,28 +564,48 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Sets locale reference to this command's description.
* It is used to display the help of this command.
* Sets whether this command should only be run in the console. This is for
* commands that dump a lot of data or are for debugging. The default value
* provided when instantiating this CompositeCommand is {@code false}.
* Therefore, this method should only be used in case you want to explicitly
* edit the value.
*
* @param onlyConsole {@code true} if this command should only be run in the
* console.
* @since 1.24.0
*/
public void setOnlyConsole(boolean onlyConsole) {
this.onlyConsole = onlyConsole;
}
/**
* Sets locale reference to this command's description. It is used to display
* the help of this command.
*
* <br/><br/>
* <br/>
* <br/>
*
* A default value is provided when instantiating this CompositeCommand:
*
* <ul>
* <li>{@code "commands." + getLabel() + ".description"} if this is a top-level command;</li>
* <li>{@code "commands." + getParent.getLabel() + getLabel() + ".description"} if this is a sub-command.
* <br/>
* Note that it can have up to 20 parent commands' labels being inserted before this sub-command's label.
* Here are a few examples :
* <ul>
* <li>/bentobox info : {@code "commands.bentobox.info.description"};</li>
* <li>/bsbadmin range set : {@code "commands.bsbadmin.range.set.description"};</li>
* <li>/mycommand sub1 sub2 sub3 [...] sub22 : {@code "commands.sub3.[...].sub20.sub21.sub22.description"}.</li>
* </ul>
* </li>
* <li>{@code "commands." + getLabel() + ".description"} if this is a top-level
* command;</li>
* <li>{@code "commands." + getParent.getLabel() + getLabel() + ".description"}
* if this is a sub-command. <br/>
* Note that it can have up to 20 parent commands' labels being inserted before
* this sub-command's label. Here are a few examples :
* <ul>
* <li>/bentobox info : {@code "commands.bentobox.info.description"};</li>
* <li>/bsbadmin range set :
* {@code "commands.bsbadmin.range.set.description"};</li>
* <li>/mycommand sub1 sub2 sub3 [...] sub22 :
* {@code "commands.sub3.[...].sub20.sub21.sub22.description"}.</li>
* </ul>
* </li>
* </ul>
*
* This method should therefore only be used in case you want to provide a different value than the default one.
* This method should therefore only be used in case you want to provide a
* different value than the default one.
*
* @param description The locale command's description reference to set.
* @return The instance of this {@link Command}.
@ -537,28 +617,33 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* Sets locale reference to this command's parameters.
* It is used to display the help of this command.
* Sets locale reference to this command's parameters. It is used to display the
* help of this command.
*
* <br/><br/>
* <br/>
* <br/>
*
* A default value is provided when instantiating this CompositeCommand:
*
* <ul>
* <li>{@code "commands." + getLabel() + ".parameters"} if this is a top-level command;</li>
* <li>{@code "commands." + getParent.getLabel() + getLabel() + ".parameters"} if this is a sub-command.
* <br/>
* Note that it can have up to 20 parent commands' labels being inserted before this sub-command's label.
* Here are a few examples :
* <ul>
* <li>/bentobox info : {@code "commands.bentobox.info.parameters"};</li>
* <li>/bsbadmin range set : {@code "commands.bsbadmin.range.set.parameters"};</li>
* <li>/mycommand sub1 sub2 sub3 [...] sub22 : {@code "commands.sub3.[...].sub20.sub21.sub22.parameters"}.</li>
* </ul>
* </li>
* <li>{@code "commands." + getLabel() + ".parameters"} if this is a top-level
* command;</li>
* <li>{@code "commands." + getParent.getLabel() + getLabel() + ".parameters"}
* if this is a sub-command. <br/>
* Note that it can have up to 20 parent commands' labels being inserted before
* this sub-command's label. Here are a few examples :
* <ul>
* <li>/bentobox info : {@code "commands.bentobox.info.parameters"};</li>
* <li>/bsbadmin range set :
* {@code "commands.bsbadmin.range.set.parameters"};</li>
* <li>/mycommand sub1 sub2 sub3 [...] sub22 :
* {@code "commands.sub3.[...].sub20.sub21.sub22.parameters"}.</li>
* </ul>
* </li>
* </ul>
*
* This method should therefore only be used in case you want to provide a different value than the default one.
* This method should therefore only be used in case you want to provide a
* different value than the default one.
*
* @param parametersHelp The locale command's paramaters reference to set.
*/
@ -566,12 +651,15 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
this.parameters = parametersHelp;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
*
* @see org.bukkit.command.Command#setPermission(java.lang.String)
*/
@Override
public void setPermission(String permission) {
this.permission = ((permissionPrefix != null && !permissionPrefix.isEmpty()) ? permissionPrefix : "") + permission;
this.permission = ((permissionPrefix != null && !permissionPrefix.isEmpty()) ? permissionPrefix : "")
+ permission;
}
/**
@ -600,24 +688,31 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
@Override
@NonNull
public List<String> tabComplete(final @NonNull CommandSender sender, final @NonNull String alias, final String[] args) {
List<String> options = new ArrayList<>();
public List<String> tabComplete(final @NonNull CommandSender sender, final @NonNull String alias,
final String[] args) {
// Get command object based on args entered so far
CompositeCommand command = getCommandFromArgs(args);
// Check for console and permissions
if (command.isOnlyPlayer() && !(sender instanceof Player)) {
return options;
if ((command.isOnlyPlayer() && !(sender instanceof Player))
|| (command.isOnlyConsole() && sender instanceof Player)) {
return List.of();
}
if (command.getPermission() != null && !command.getPermission().isEmpty() && !sender.hasPermission(command.getPermission()) && !sender.isOp()) {
return options;
if (command.getPermission() != null && !command.getPermission().isEmpty()
&& !sender.hasPermission(command.getPermission()) && !sender.isOp()) {
return List.of();
}
// Add any tab completion from the subcommand
options.addAll(command.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args))).orElseGet(ArrayList::new));
List<String> options = new ArrayList<>(
command.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args)))
.orElseGet(ArrayList::new));
if (command.hasSubCommands()) {
options.addAll(getSubCommandLabels(sender, command));
}
/* /!\ The following check is likely a poor quality patch-up job. If any better solution can be applied, don't hesitate to do so. */
/*
* /!\ The following check is likely a poor quality patch-up job. If any better
* solution can be applied, don't hesitate to do so.
*/
// See https://github.com/BentoBoxWorld/BentoBox/issues/416
// "help" shouldn't appear twice, so remove it if it is already in the args.
@ -628,31 +723,44 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/* ------------ */
String lastArg = args.length != 0 ? args[args.length - 1] : "";
return Util.tabLimit(options, lastArg).stream().sorted().collect(Collectors.toList());
return Util.tabLimit(options, lastArg).stream().sorted().toList();
}
/**
* Returns a list containing all the labels of the subcommands for the provided CompositeCommand excluding any hidden commands
* @param sender the CommandSender
* Returns a list containing all the labels of the subcommands for the provided
* CompositeCommand excluding any hidden commands
*
* @param sender the CommandSender
* @param command the CompositeCommand to get the subcommands from
* @return a list of subcommands labels or an empty list.
*/
@NonNull
private List<String> getSubCommandLabels(@NonNull CommandSender sender, @NonNull CompositeCommand command) {
return command.getSubCommands().values().stream()
.filter(cmd -> !cmd.isHidden())
.filter(cmd -> !cmd.isOnlyPlayer() || sender.isOp() || (sender instanceof Player && cmd.getPermission() != null && (cmd.getPermission().isEmpty() || sender.hasPermission(cmd.getPermission()))) )
.map(CompositeCommand::getLabel).collect(Collectors.toList());
List<String> result = new ArrayList<>();
for (CompositeCommand cc : command.getSubCommands().values()) {
// Player or not
if (sender instanceof Player) {
if (!cc.isHidden() && !cc.isOnlyConsole()
&& (cc.getPermission().isEmpty() || sender.hasPermission(cc.getPermission()))) {
result.add(cc.getLabel());
}
} else if (!cc.isOnlyPlayer()) {
result.add(cc.getLabel());
}
}
return result;
}
/**
* Show help
*
* @param command - command that this help is for
* @param user - the User
* @param user - the User
* @return result of help command or false if no help defined
*/
protected boolean showHelp(CompositeCommand command, User user) {
return command.getSubCommand("help").map(helpCommand -> helpCommand.execute(user, helpCommand.getLabel(), new ArrayList<>())).orElse(false);
return command.getSubCommand("help")
.map(helpCommand -> helpCommand.execute(user, helpCommand.getLabel(), new ArrayList<>())).orElse(false);
}
/**
@ -663,7 +771,9 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* If the permission prefix has been set, will return the prefix plus a trailing dot.
* If the permission prefix has been set, will return the prefix plus a trailing
* dot.
*
* @return the permissionPrefix
*/
@Nullable
@ -673,6 +783,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* The the world that this command applies to.
*
* @return the world
*/
public World getWorld() {
@ -689,6 +800,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Get the parental addon
*
* @return the addon
*/
@SuppressWarnings("unchecked")
@ -705,28 +817,33 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Set a cool down - can be set by other commands on this one
* @param uniqueId - the unique ID that is having the cooldown
* @param targetUUID - the target (if any)
*
* @param uniqueId - the unique ID that is having the cooldown
* @param targetUUID - the target (if any)
* @param timeInSeconds - time in seconds to cool down
* @since 1.5.0
*/
public void setCooldown(String uniqueId, String targetUUID, int timeInSeconds) {
cooldowns.computeIfAbsent(uniqueId, k -> new HashMap<>()).put(targetUUID, System.currentTimeMillis() + timeInSeconds * 1000L);
cooldowns.computeIfAbsent(uniqueId, k -> new HashMap<>()).put(targetUUID,
System.currentTimeMillis() + timeInSeconds * 1000L);
}
/**
* Set a cool down - can be set by other commands on this one
* @param uniqueId - the UUID that is having the cooldown
* @param targetUUID - the target UUID (if any)
*
* @param uniqueId - the UUID that is having the cooldown
* @param targetUUID - the target UUID (if any)
* @param timeInSeconds - time in seconds to cool down
*/
public void setCooldown(UUID uniqueId, UUID targetUUID, int timeInSeconds) {
cooldowns.computeIfAbsent(uniqueId.toString(), k -> new HashMap<>()).put(targetUUID == null ? null : targetUUID.toString(), System.currentTimeMillis() + timeInSeconds * 1000L);
cooldowns.computeIfAbsent(uniqueId.toString(), k -> new HashMap<>()).put(
targetUUID == null ? null : targetUUID.toString(), System.currentTimeMillis() + timeInSeconds * 1000L);
}
/**
* Set a cool down for a user - can be set by other commands on this one
* @param uniqueId - the UUID that is having the cooldown
*
* @param uniqueId - the UUID that is having the cooldown
* @param timeInSeconds - time in seconds to cool down
* @since 1.5.0
*/
@ -736,7 +853,8 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if cool down is in progress for user
* @param user - the caller of the command
*
* @param user - the caller of the command
* @param targetUUID - the target (if any)
* @return true if cool down in place, false if not
*/
@ -746,6 +864,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if cool down is in progress for user
*
* @param user - the user to check
* @return true if cool down in place, false if not
* @since 1.5.0
@ -756,14 +875,16 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Check if cool down is in progress
* @param user - the caller of the command
* @param uniqueId - the id that needs to be checked
*
* @param user - the caller of the command
* @param uniqueId - the id that needs to be checked
* @param targetUUID - the target (if any)
* @return true if cool down in place, false if not
* @since 1.5.0
*/
protected boolean checkCooldown(User user, String uniqueId, String targetUUID) {
if (!cooldowns.containsKey(uniqueId) || user.isOp() || user.hasPermission(getPermissionPrefix() + "mod.bypasscooldowns")) {
if (!cooldowns.containsKey(uniqueId) || user.isOp()
|| user.hasPermission(getPermissionPrefix() + "mod.bypasscooldowns")) {
return false;
}
cooldowns.putIfAbsent(uniqueId, new HashMap<>());
@ -772,7 +893,8 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
cooldowns.get(uniqueId).remove(targetUUID);
return false;
}
int timeToGo = (int) ((cooldowns.get(uniqueId).getOrDefault(targetUUID, 0L) - System.currentTimeMillis()) / 1000);
int timeToGo = (int) ((cooldowns.get(uniqueId).getOrDefault(targetUUID, 0L) - System.currentTimeMillis())
/ 1000);
user.sendMessage("general.errors.you-must-wait", TextVariables.NUMBER, String.valueOf(timeToGo));
return true;
}
@ -813,6 +935,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Checks if a command is hidden
*
* @return the hidden
* @since 1.13.0
*/
@ -822,6 +945,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/**
* Sets a command and all its help and tab complete as hidden
*
* @param hidden whether command is hidden or not
* @since 1.13.0
*/

View File

@ -11,7 +11,8 @@ import world.bentobox.bentobox.api.user.User;
/**
* BentoBox Confirmable Command
* Adds ability to confirm a command before execution
* Adds ability to confirm a command before execution.
* See {@link #askConfirmation(User, Runnable)}, {@link #askConfirmation(User, String, Runnable)}
* @author tastybento
* @author Poslovitch
*/
@ -88,7 +89,8 @@ public abstract class ConfirmableCommand extends CompositeCommand {
}
/**
* Tells user to confirm command by retyping it.
* Tells user to confirm command by retyping it. Uses the default message to retype it.<p>
* If you need a custom message, use {@link #askConfirmation(User, String, Runnable)}
* @param user User to ask confirmation to.
* @param confirmed Runnable to be executed if successfully confirmed.
*/

View File

@ -1,11 +1,13 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -18,6 +20,9 @@ import world.bentobox.bentobox.util.Util;
public class AdminDeleteCommand extends ConfirmableCommand {
private @Nullable UUID targetUUID;
private Island island;
public AdminDeleteCommand(CompositeCommand parent) {
super(parent, "delete");
}
@ -31,71 +36,101 @@ public class AdminDeleteCommand extends ConfirmableCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() != 1) {
showHelp(this, user);
if (args.isEmpty()) {
this.showHelp(this, user);
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
// Convert name to a UUID
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
UUID owner = getIslands().getOwner(getWorld(), targetUUID);
if (owner == null) {
// Check island exists
if (!getIslands().hasIsland(getWorld(), targetUUID) && !getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (args.size() == 1) {
// Check if player is owner of any islands
if (getIslands().getIslands(getWorld(), targetUUID).stream().filter(Island::hasTeam)
.anyMatch(is -> targetUUID.equals(is.getOwner()))) {
user.sendMessage("commands.admin.delete.cannot-delete-owner");
return false;
}
// This is a delete everything request
return true;
}
// Get the island
User target = User.getInstance(targetUUID);
// They named the island to go to
Map<String, IslandInfo> names = getNameIslandMap(target);
final String name = String.join(" ", args.subList(1, args.size()));
if (!names.containsKey(name)) {
// Failed home name check
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
names.keySet()
.forEach(n -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, n));
return false;
} else {
IslandInfo info = names.get(name);
island = info.island;
}
// Team members should be kicked before deleting otherwise the whole team will become weird
if (getIslands().inTeam(getWorld(), targetUUID) && owner.equals(targetUUID)) {
if (island.hasTeam() && targetUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.delete.cannot-delete-owner");
return false;
}
if (names.size() == 1) {
// This is the only island they have so, no need to specify it
island = null;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Get target
UUID targetUUID = getPlayers().getUUID(args.get(0));
// Confirm
askConfirmation(user, () -> deletePlayer(user, targetUUID));
if (island == null) {
// Delete the player entirely
askConfirmation(user, () -> deletePlayer(user));
} else {
// Just delete the player's island
askConfirmation(user, () -> deleteIsland(user, island));
}
return true;
}
private void deletePlayer(User user, UUID targetUUID) {
// Delete player and island
// Get the target's island
Island oldIsland = getIslands().getIsland(getWorld(), targetUUID);
Vector vector = null;
if (oldIsland != null) {
// Fire island preclear event
IslandEvent.builder()
.involvedPlayer(user.getUniqueId())
.reason(Reason.PRECLEAR)
.island(oldIsland)
.oldIsland(oldIsland)
.location(oldIsland.getCenter())
.build();
// Check if player is online and on the island
User target = User.getInstance(targetUUID);
// Remove them from this island (it still exists and will be deleted later)
getIslands().removePlayer(getWorld(), targetUUID);
if (target.isPlayer() && target.isOnline()) {
cleanUp(user, target);
}
vector = oldIsland.getCenter().toVector();
getIslands().deleteIsland(oldIsland, true, targetUUID);
}
if (vector == null) {
user.sendMessage("general.success");
} else {
user.sendMessage("commands.admin.delete.deleted-island", TextVariables.XYZ, Util.xyz(vector));
}
private void deleteIsland(User user, Island oldIsland) {
// Fire island preclear event
IslandEvent.builder().involvedPlayer(user.getUniqueId()).reason(Reason.PRECLEAR).island(oldIsland)
.oldIsland(oldIsland).location(oldIsland.getCenter()).build();
user.sendMessage("commands.admin.delete.deleted-island", TextVariables.XYZ,
Util.xyz(oldIsland.getCenter().toVector()));
getIslands().deleteIsland(oldIsland, true, targetUUID);
}
private void cleanUp(User user, User target) {
private void deletePlayer(User user) {
// Delete player and island
for (Island oldIsland : getIslands().getIslands(getWorld(), targetUUID)) {
deleteIsland(user, oldIsland);
}
// Check if player is online and on the island
User target = User.getInstance(targetUUID);
// Remove target from any and all islands in the world
getIslands().removePlayer(getWorld(), targetUUID);
if (target.isPlayer() && target.isOnline()) {
cleanUp(target);
}
user.sendMessage("general.success");
}
private void cleanUp(User target) {
// Remove money inventory etc.
if (getIWM().isOnLeaveResetEnderChest(getWorld())) {
target.getPlayer().getEnderChest().clear();
@ -118,11 +153,40 @@ public class AdminDeleteCommand extends ConfirmableCommand {
// Reset the XP
if (getIWM().isOnLeaveResetXP(getWorld())) {
// Player collected XP (displayed)
target.getPlayer().setLevel(0);
target.getPlayer().setExp(0);
// Player total XP (not displayed)
target.getPlayer().setTotalExperience(0);
}
// Execute commands when leaving
Util.runCommands(target, getIWM().getOnLeaveCommands(getWorld()), "leave");
Util.runCommands(target, target.getName(), getIWM().getOnLeaveCommands(getWorld()), "leave");
}
private record IslandInfo(Island island, boolean islandName) {
}
private Map<String, IslandInfo> getNameIslandMap(User target) {
Map<String, IslandInfo> islandMap = new HashMap<>();
int index = 0;
for (Island island : getIslands().getIslands(getWorld(), target.getUniqueId())) {
index++;
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
islandMap.put(island.getName(), new IslandInfo(island, true));
} else {
// Name has not been set
String text = target.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME,
target.getName(), TextVariables.DISPLAY_NAME, target.getDisplayName()) + " " + index;
islandMap.put(text, new IslandInfo(island, true));
}
// Add homes. Homes do not need an island specified
island.getHomes().keySet().forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
}
return islandMap;
}
@Override
@ -132,7 +196,16 @@ public class AdminDeleteCommand extends ConfirmableCommand {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
if (args.size() == 2) {
return Optional.of(Util.tabLimit(new ArrayList<>(Util.getOnlinePlayerList(user)), lastArg));
}
if (args.size() == 3) {
UUID target = Util.getUUID(args.get(1));
return target == null ? Optional.empty()
: Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(User.getInstance(target)).keySet()),
lastArg));
}
return Optional.empty();
}
}

View File

@ -55,11 +55,11 @@ public class AdminDeleteHomesCommand extends ConfirmableCommand {
return false;
}
// Confirm
askConfirmation(user, user.getTranslation("commands.admin.deletehomes.warning"), () -> deleteHomes(user, targetUUID, island));
askConfirmation(user, user.getTranslation("commands.admin.deletehomes.warning"), () -> deleteHomes(user, island));
return true;
}
private boolean deleteHomes(User user, UUID targetUUID, Island island) {
private boolean deleteHomes(User user, Island island) {
island.removeHomes();
user.sendMessage("general.success");
return true;

View File

@ -1,61 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
public class AdminEmptyTrashCommand extends ConfirmableCommand {
/**
* Clear trash for player, or all unowned islands in trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminEmptyTrashCommand(CompositeCommand parent) {
super(parent, "emptytrash");
}
@Override
public void setup() {
setPermission("admin.trash");
setOnlyPlayer(false);
setParametersHelp("commands.admin.emptytrash.parameters");
setDescription("commands.admin.emptytrash.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() > 1) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
UUID targetUUID = args.isEmpty() ? null : getPlayers().getUUID(args.get(0));
if (!args.isEmpty() && targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Remove trash for this player
final List<Island> islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
if (args.isEmpty()) {
user.sendMessage("commands.admin.trash.no-unowned-in-trash");
} else {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
}
return false;
} else {
this.askConfirmation(user, () -> {
getIslands().deleteQuarantinedIslandByUser(getWorld(), targetUUID);
user.sendMessage("commands.admin.emptytrash.success");
});
return true;
}
}
}

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -17,6 +16,7 @@ import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Tells the rank of the player
* @author tastybento
*
*/
@ -83,10 +83,10 @@ public class AdminGetrankCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Get rank
RanksManager rm = getPlugin().getRanksManager();
User target = User.getInstance(targetUUID);
int currentRank = island.getRank(target);
user.sendMessage("commands.admin.getrank.rank-is", TextVariables.RANK, user.getTranslation(rm.getRank(currentRank)),
user.sendMessage("commands.admin.getrank.rank-is", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(currentRank)),
TextVariables.NAME, getPlayers().getName(island.getOwner()));
return true;
}
@ -98,7 +98,7 @@ public class AdminGetrankCommand extends CompositeCommand {
return Optional.empty();
}
String lastArg = args.get(args.size() - 1);
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -35,7 +35,7 @@ public class AdminInfoCommand extends CompositeCommand {
}
// If there are no args, then the player wants info on the island at this location
if (args.isEmpty()) {
getIslands().getIslandAt(user.getLocation()).ifPresentOrElse(i -> new IslandInfo(i).showAdminInfo(user), () ->
getIslands().getIslandAt(user.getLocation()).ifPresentOrElse(i -> new IslandInfo(i).showAdminInfo(user, getAddon()), () ->
user.sendMessage("commands.admin.info.no-island"));
return true;
}
@ -48,10 +48,7 @@ public class AdminInfoCommand extends CompositeCommand {
// Show info for this player
Island island = getIslands().getIsland(getWorld(), targetUUID);
if (island != null) {
new IslandInfo(island).showAdminInfo(user);
if (!getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID).isEmpty()) {
user.sendMessage("commands.admin.info.islands-in-trash");
}
new IslandInfo(island).showAdminInfo(user, getAddon());
return true;
} else {
user.sendMessage("general.errors.player-has-no-island");

View File

@ -2,11 +2,13 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -19,6 +21,10 @@ import world.bentobox.bentobox.util.Util;
public class AdminRegisterCommand extends ConfirmableCommand {
private Island island;
private Location closestIsland;
private @Nullable UUID targetUUID;
public AdminRegisterCommand(CompositeCommand parent) {
super(parent, "register");
}
@ -32,104 +38,108 @@ public class AdminRegisterCommand extends ConfirmableCommand {
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
showHelp(this, user);
return false;
}
// Check world
if (!getWorld().equals(user.getWorld())) {
user.sendMessage("general.errors.wrong-world");
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (getIslands().hasIsland(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-has-island");
return false;
}
if (getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("commands.admin.register.cannot-register-team-player");
return false;
}
// Check if this spot is still being deleted
Location closestIsland = Util.getClosestIsland(user.getLocation());
closestIsland = Util.getClosestIsland(user.getLocation());
if (getPlugin().getIslandDeletionManager().inDeletion(closestIsland)) {
user.sendMessage("commands.admin.register.in-deletion");
return false;
}
// Check if island is owned
Optional<Island> island = getIslands().getIslandAt(user.getLocation());
if (island.filter(Island::isOwned)
.filter(i -> !i.getOwner().equals(targetUUID))
.isPresent()) {
Optional<Island> opIsland = getIslands().getIslandAt(user.getLocation());
if (opIsland.isEmpty()) {
// Reserve spot
this.askConfirmation(user, user.getTranslation("commands.admin.register.no-island-here"),
() -> reserve(user, args.get(0)));
return false;
}
island = opIsland.get();
if (targetUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.register.already-owned");
return false;
}
// Check if island is spawn
if (island.map(Island::isSpawn).orElse(false)) {
askConfirmation(user, user.getTranslation("commands.admin.register.island-is-spawn"), () -> register(user, args.get(0), targetUUID, island, closestIsland));
if (island.isSpawn()) {
askConfirmation(user, user.getTranslation("commands.admin.register.island-is-spawn"),
() -> register(user, args.get(0)));
return false;
}
return register(user, args.get(0),targetUUID, island, closestIsland);
return true;
}
private boolean register(User user, String targetName, UUID targetUUID, Optional<Island> island, Location closestIsland) {
// Register island if it exists
if (!island.map(i -> {
// Island exists
getIslands().setOwner(user, targetUUID, i);
if (i.isSpawn()) {
getIslands().clearSpawn(i.getWorld());
}
user.sendMessage("commands.admin.register.registered-island", TextVariables.XYZ, Util.xyz(i.getCenter().toVector()),
TextVariables.NAME, targetName);
user.sendMessage("general.success");
// Build and call event
IslandEvent.builder()
.island(i)
.location(i.getCenter())
.reason(IslandEvent.Reason.REGISTERED)
.involvedPlayer(targetUUID)
.admin(true)
.build();
IslandEvent.builder()
.island(i)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.VISITOR_RANK, RanksManager.OWNER_RANK)
.build();
return true;
}).orElse(false)) {
// Island does not exist - this is a reservation
this.askConfirmation(user, user.getTranslation("commands.admin.register.no-island-here"), () -> {
// Make island here
Island i = getIslands().createIsland(closestIsland, targetUUID);
if (i == null) {
user.sendMessage("commands.admin.register.cannot-make-island");
return;
}
getIslands().setOwner(user, targetUUID, i);
i.setReserved(true);
i.getCenter().getBlock().setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.reserved-island", TextVariables.XYZ, Util.xyz(i.getCenter().toVector()),
TextVariables.NAME, targetName);
// Build and fire event
IslandEvent.builder()
.island(i)
.location(i.getCenter())
.reason(IslandEvent.Reason.RESERVED)
.involvedPlayer(targetUUID)
.admin(true)
.build();
});
return false;
}
@Override
public boolean execute(User user, String label, List<String> args) {
register(user, args.get(0));
return true;
}
/**
* Reserve a spot for a target
* @param user user doing the reserving
* @param targetName target name
*/
void reserve(User user, String targetName) {
Objects.requireNonNull(closestIsland);
Objects.requireNonNull(targetUUID);
// Island does not exist - this is a reservation
// Make island here
Island i = getIslands().createIsland(closestIsland, targetUUID);
if (i == null) {
user.sendMessage("commands.admin.register.cannot-make-island");
return;
}
getIslands().setOwner(user, targetUUID, i, RanksManager.VISITOR_RANK);
i.setReserved(true);
i.getCenter().getBlock().setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.reserved-island", TextVariables.XYZ,
Util.xyz(i.getCenter().toVector()), TextVariables.NAME, targetName);
// Build and fire event
IslandEvent.builder().island(i).location(i.getCenter()).reason(IslandEvent.Reason.RESERVED)
.involvedPlayer(targetUUID).admin(true).build();
}
/**
* Register the island to a target
* @param user user doing the registering
* @param targetName name of target
*/
void register(User user, String targetName) {
Objects.requireNonNull(closestIsland);
Objects.requireNonNull(targetUUID);
Objects.requireNonNull(island);
// Island exists
getIslands().setOwner(user, targetUUID, island, RanksManager.VISITOR_RANK);
if (island.isSpawn()) {
getIslands().clearSpawn(island.getWorld());
}
user.sendMessage("commands.admin.register.registered-island", TextVariables.XYZ,
Util.xyz(island.getCenter().toVector()), TextVariables.NAME, targetName);
user.sendMessage("general.success");
// Build and call event
IslandEvent.builder().island(island).location(island.getCenter()).reason(IslandEvent.Reason.REGISTERED)
.involvedPlayer(targetUUID).admin(true).build();
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(RanksManager.VISITOR_RANK, RanksManager.OWNER_RANK)
.build();
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -26,7 +25,7 @@ public class AdminResetFlagsCommand extends ConfirmableCommand {
super(parent, "resetflags");
options = getPlugin().getFlagsManager().getFlags().stream()
.filter(f -> f.getType().equals(Type.PROTECTION) || f.getType().equals(Type.SETTING))
.map(Flag::getID).collect(Collectors.toList());
.map(Flag::getID).toList();
}
@Override

View File

@ -0,0 +1,130 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
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;
/**
* This command resets players island name.
* @author BONNe
*/
public class AdminResetNameCommand extends CompositeCommand
{
/**
* Default constructor.
* @param command Parent command.
*/
public AdminResetNameCommand(CompositeCommand command)
{
super(command, "resetname");
}
/**
* {@inheritDoc}
*/
@Override
public void setup()
{
this.setPermission("mod.resetname");
this.setOnlyPlayer(true);
this.setDescription("commands.admin.resetname.description");
}
/**
* @param user the {@link User} who is executing this command.
* @param label the label which has been used to execute this command.
* It can be {@link CompositeCommand#getLabel()} or an alias.
* @param args the command arguments.
* @return {@code true} if name can be reset, {@code false} otherwise.
*/
@Override
public boolean canExecute(User user, String label, List<String> args)
{
if (args.size() == 1)
{
UUID playerUUID = Util.getUUID(args.get(0));
if (playerUUID == null)
{
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
this.island = this.getIslandsManager().getIsland(this.getWorld(), playerUUID);
}
else
{
this.island = this.getIslandsManager().getIslandAt(user.getLocation()).orElse(null);
}
if (this.island == null)
{
user.sendMessage("general.errors.player-has-no-island");
return false;
}
return true;
}
/**
* @param user the {@link User} who is executing this command.
* @param label the label which has been used to execute this command.
* It can be {@link CompositeCommand#getLabel()} or an alias.
* @param args the command arguments.
* @return {@code true}
*/
@Override
public boolean execute(User user, String label, List<String> args)
{
if (this.island == null)
{
this.showHelp(this, user);
return true;
}
// Resets the island name
this.island.setName(null);
user.sendMessage("commands.admin.resetname.success", TextVariables.NAME, this.getPlayers().getName(this.island.getOwner()));
return true;
}
/**
* @param user the {@link User} who is executing this command.
* @param alias alias for command
* @param args command arguments
* @return Optional of possible values.
*/
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
// Return the player names
if (args.size() == 1)
{
return Optional.of(Util.getOnlinePlayerList(user));
}
else
{
return Optional.empty();
}
}
/**
* Island which name must be changed.
*/
@Nullable
private Island island;
}

View File

@ -50,7 +50,7 @@ public class AdminSetProtectionCenterCommand extends ConfirmableCommand
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() == 3) {
// Get location
targetLoc = getLocation(user, args);
targetLoc = getLocation(args);
} else {
targetLoc = new Location(getWorld(), user.getLocation().getBlockX(), user.getLocation().getBlockY(), user.getLocation().getBlockZ());
}
@ -67,7 +67,7 @@ public class AdminSetProtectionCenterCommand extends ConfirmableCommand
return true;
}
private Location getLocation(User user, List<String> args) {
private Location getLocation(List<String> args) {
try {
int x = Integer.parseInt(args.get(0));
int y = Integer.parseInt(args.get(1));

View File

@ -1,6 +1,5 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -12,84 +11,70 @@ import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
/**
* This command sets spawn point for island at admin location for island on which admin is located.
* This command is only for player entity.
* @author BONNe
* @since 1.13.0
*/
public class AdminSetSpawnPointCommand extends ConfirmableCommand
{
public class AdminSetSpawnPointCommand extends ConfirmableCommand {
/**
* Sub-command constructor
*
* @param parent - the parent composite command
*/
public AdminSetSpawnPointCommand(CompositeCommand parent)
{
public AdminSetSpawnPointCommand(CompositeCommand parent) {
super(parent, "setspawnpoint");
}
/**
* {@inheritDoc}
*/
@Override
public void setup()
{
public void setup() {
this.setPermission("admin.setspawnpoint");
this.setOnlyPlayer(true);
this.setDescription("commands.admin.setspawnpoint.description");
}
/**
* This method finds an island in user location and asks confirmation if spawn point
* must be changed to that location.
* This method finds an island in user location and asks confirmation if spawn point
* must be changed to that location.
* {@inheritDoc}
*/
@Override
public boolean execute(User user, String label, List<String> args)
{
public boolean execute(User user, String label, List<String> args) {
Optional<Island> optionalIsland = this.getIslands().getIslandAt(user.getLocation());
if (optionalIsland.isPresent() &&
(optionalIsland.get().hasNetherIsland() ||
!World.Environment.NETHER.equals(user.getLocation().getWorld().getEnvironment())) &&
(optionalIsland.get().hasEndIsland() ||
!World.Environment.THE_END.equals(user.getLocation().getWorld().getEnvironment())))
{
if (optionalIsland.isPresent()
&& (optionalIsland.get().hasNetherIsland()
|| !World.Environment.NETHER.equals(user.getLocation().getWorld().getEnvironment()))
&& (optionalIsland.get().hasEndIsland()
|| !World.Environment.THE_END.equals(user.getLocation().getWorld().getEnvironment()))) {
// Everything's fine, we can set the location as spawn point for island :)
this.askConfirmation(user, user.getTranslation("commands.admin.setspawnpoint.confirmation"),
() -> this.setSpawnPoint(user, optionalIsland.get()));
() -> this.setSpawnPoint(user, optionalIsland.get()));
return true;
}
else
{
} else {
user.sendMessage("commands.admin.setspawnpoint.no-island-here");
return false;
}
}
/**
* This method changes spawn point for island at given user location.
* @param user User who initiate spawn point change.
* @param island Island which is targeted by user.
*/
private void setSpawnPoint(User user, Island island)
{
private void setSpawnPoint(User user, Island island) {
island.setSpawnPoint(Objects.requireNonNull(user.getLocation().getWorld()).getEnvironment(),
user.getLocation());
user.getLocation());
user.sendMessage("commands.admin.setspawnpoint.success");
if (!island.isSpawn())
{
island.getPlayersOnIsland().forEach(player ->
User.getInstance(player).sendMessage("commands.admin.setspawnpoint.island-spawnpoint-changed",
"[user]", user.getName()));
if (!island.isSpawn()) {
island.getPlayersOnIsland().forEach(player -> User.getInstance(player)
.sendMessage("commands.admin.setspawnpoint.island-spawnpoint-changed", "[user]", user.getName()));
}
}
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
@ -25,7 +24,6 @@ public class AdminSetrankCommand extends CompositeCommand {
private int rankValue;
private @Nullable UUID targetUUID;
private @Nullable UUID ownerUUID;
private RanksManager rm;
public AdminSetrankCommand(CompositeCommand adminCommand) {
super(adminCommand, "setrank");
@ -37,7 +35,6 @@ public class AdminSetrankCommand extends CompositeCommand {
setOnlyPlayer(false);
setParametersHelp("commands.admin.setrank.parameters");
setDescription("commands.admin.setrank.description");
rm = getPlugin().getRanksManager();
}
@Override
@ -54,7 +51,7 @@ public class AdminSetrankCommand extends CompositeCommand {
return false;
}
// Get rank
rankValue = rm.getRanks().entrySet().stream()
rankValue = RanksManager.getInstance().getRanks().entrySet().stream()
.filter(r -> user.getTranslation(r.getKey()).equalsIgnoreCase(args.get(1))).findFirst()
.map(Map.Entry::getValue).orElse(-999);
if (rankValue < RanksManager.BANNED_RANK) {
@ -122,8 +119,8 @@ public class AdminSetrankCommand extends CompositeCommand {
ownerName = target.getName();
}
user.sendMessage("commands.admin.setrank.rank-set",
"[from]", user.getTranslation(rm.getRank(currentRank)),
"[to]", user.getTranslation(rm.getRank(rankValue)),
"[from]", user.getTranslation(RanksManager.getInstance().getRank(currentRank)), "[to]",
user.getTranslation(RanksManager.getInstance().getRank(rankValue)),
TextVariables.NAME, ownerName);
return true;
}
@ -137,10 +134,10 @@ public class AdminSetrankCommand extends CompositeCommand {
// Return the ranks
if (args.size() == 3) {
return Optional.of(getPlugin().getRanksManager().getRanks()
return Optional.of(RanksManager.getInstance().getRanks()
.entrySet().stream()
.filter(entry -> entry.getValue() > RanksManager.VISITOR_RANK)
.map(entry -> user.getTranslation(entry.getKey())).collect(Collectors.toList()));
.map(entry -> user.getTranslation(entry.getKey())).toList());
}
// Return the player names again for the optional island owner argument

View File

@ -65,8 +65,8 @@ public class AdminSetspawnCommand extends ConfirmableCommand {
.build();
}
// If island is owned, then unregister the owner and any members
new ImmutableSet.Builder<UUID>().addAll(i.getMembers().keySet()).build().forEach(m ->
getIslands().removePlayer(getWorld(), m));
new ImmutableSet.Builder<UUID>().addAll(i.getMembers().keySet()).build()
.forEach(m -> getIslands().removePlayer(i, m));
}
getIslands().setSpawn(i);
i.setSpawnPoint(World.Environment.NORMAL, user.getLocation());

View File

@ -8,7 +8,6 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.eclipse.jdt.annotation.NonNull;
@ -17,7 +16,6 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.Flag.Mode;
import world.bentobox.bentobox.api.flags.Flag.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder;
@ -88,11 +86,9 @@ public class AdminSettingsCommand extends CompositeCommand {
}
private boolean getIsland(User user, List<String> args) {
if (args.get(0).equalsIgnoreCase(SPAWN_ISLAND)) {
if (getIslands().getSpawn(getWorld()).isPresent()) {
island = getIslands().getSpawn(getWorld()).get();
return true;
}
if (args.get(0).equalsIgnoreCase(SPAWN_ISLAND) && getIslands().getSpawn(getWorld()).isPresent()) {
island = getIslands().getSpawn(getWorld()).get();
return true;
}
// Get target player
@Nullable UUID targetUUID = Util.getUUID(args.get(0));
@ -110,51 +106,66 @@ public class AdminSettingsCommand extends CompositeCommand {
/**
* Check that this command is correct to set a setting
*
* @param user - user
* @param args - args
* @return true if the syntax is correct
*/
private boolean checkSyntax(User user, List<String> args) {
// Update the flag lists
this.makeLists();
if (args.size() == 2) {
// Should be a world setting
// If world settings, then active/disabled, otherwise player flags
if (worldSettingFlagNames.contains(args.get(0).toUpperCase(Locale.ENGLISH))) {
if (checkActiveDisabled(user, args.get(1))) {
flag = getPlugin().getFlagsManager().getFlag(args.get(0).toUpperCase(Locale.ENGLISH));
return true;
}
} else {
this.showHelp(this, user);
return false;
}
} else if (args.size() > 2) {
// Get island
if (!getIsland(user, args)) {
return false;
}
if (!settingFlagNames.contains(args.get(1).toUpperCase(Locale.ENGLISH))
&& !protectionFlagNames.contains(args.get(1).toUpperCase(Locale.ENGLISH))) {
user.sendMessage("commands.admin.settings.unknown-flag", TextVariables.NAME, args.get(2));
return false;
}
// Set flag
flag = getPlugin().getFlagsManager().getFlag(args.get(1).toUpperCase(Locale.ENGLISH));
// Check settings
if (flag.isPresent()) {
if (flag.get().getType().equals(Type.SETTING)) {
return checkActiveDisabled(user, args.get(2));
} else {
// Protection flag
return checkRank(user, String.join(" ", args.subList(2, args.size())));
}
int argSize = args.size();
if (argSize == 2) {
return checkWorldSetting(user, args);
} else if (argSize > 2) {
return checkIslandSetting(user, args);
}
return false;
}
private boolean checkWorldSetting(User user, List<String> args) {
String arg0 = args.get(0).toUpperCase(Locale.ENGLISH);
if (worldSettingFlagNames.contains(arg0)) {
if (checkActiveDisabled(user, args.get(1))) {
flag = getPlugin().getFlagsManager().getFlag(args.get(0).toUpperCase(Locale.ENGLISH));
return true;
}
} else {
this.showHelp(this, user);
return false;
}
return false;
}
private boolean checkIslandSetting(User user, List<String> args) {
// Get island
if (!getIsland(user, args)) {
return false;
}
String arg1 = args.get(1).toUpperCase(Locale.ENGLISH);
if (!settingFlagNames.contains(arg1) && !protectionFlagNames.contains(arg1)) {
user.sendMessage("commands.admin.settings.unknown-flag", TextVariables.NAME, args.get(2));
return false;
}
// Set flag
flag = getPlugin().getFlagsManager().getFlag(arg1);
// Check settings
if (flag.isPresent()) {
if (flag.get().getType().equals(Type.SETTING)) {
return checkActiveDisabled(user, args.get(2));
} else {
// Protection flag
return checkRank(user, String.join(" ", args.subList(2, args.size())));
}
}
return false;
}
/**
* Check the rank given.
@ -163,7 +174,7 @@ public class AdminSettingsCommand extends CompositeCommand {
* @return true if rank is valid
*/
private boolean checkRank(User user, String string) {
for (Entry<String, Integer> en : getPlugin().getRanksManager().getRanks().entrySet()) {
for (Entry<String, Integer> en : RanksManager.getInstance().getRanks().entrySet()) {
if (en.getValue() > RanksManager.BANNED_RANK && en.getValue() <= RanksManager.OWNER_RANK
&& string.equalsIgnoreCase(ChatColor.stripColor(user.getTranslation(en.getKey())))) {
// We have a winner
@ -192,19 +203,16 @@ public class AdminSettingsCommand extends CompositeCommand {
// Command line setting
flag.ifPresent(f -> {
switch (f.getType()) {
case PROTECTION:
case PROTECTION -> {
island.setFlag(f, rank);
getIslands().save(island);
break;
case SETTING:
}
case SETTING -> {
island.setSettingsFlag(f, activeState);
getIslands().save(island);
break;
case WORLD_SETTING:
f.setSetting(getWorld(), activeState);
break;
default:
break;
}
case WORLD_SETTING -> f.setSetting(getWorld(), activeState);
default -> {
// Do nothing
}
}
});
user.sendMessage("general.success");
@ -215,12 +223,11 @@ public class AdminSettingsCommand extends CompositeCommand {
user.sendMessage("general.errors.use-in-game");
return false;
}
getPlayers().setFlagsDisplayMode(user.getUniqueId(), Mode.EXPERT);
if (args.isEmpty()) {
new TabbedPanelBuilder()
.user(user)
.world(getWorld())
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.WORLD_SETTING))
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.WORLD_SETTING, Flag.Mode.EXPERT))
.tab(2, new WorldDefaultSettingsTab(getWorld(), user))
.startingSlot(1)
.size(54)
@ -231,8 +238,8 @@ public class AdminSettingsCommand extends CompositeCommand {
new TabbedPanelBuilder()
.user(user)
.world(island.getWorld())
.tab(1, new SettingsTab(user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, island, Flag.Type.SETTING))
.island(island).tab(1, new SettingsTab(getWorld(), user, Flag.Type.PROTECTION, Flag.Mode.EXPERT))
.tab(2, new SettingsTab(getWorld(), user, Flag.Type.SETTING, Flag.Mode.EXPERT))
.startingSlot(1)
.size(54)
.build().openPanel();
@ -266,11 +273,10 @@ public class AdminSettingsCommand extends CompositeCommand {
} else if (args.size() == 4) {
// Get flag in previous argument
options = getPlugin().getFlagsManager().getFlag(args.get(2).toUpperCase(Locale.ENGLISH)).map(f -> switch (f.getType()) {
case PROTECTION -> getPlugin().getRanksManager()
.getRanks().entrySet().stream()
case PROTECTION -> RanksManager.getInstance().getRanks().entrySet().stream()
.filter(en -> en.getValue() > RanksManager.BANNED_RANK && en.getValue() <= RanksManager.OWNER_RANK)
.map(Entry::getKey)
.map(user::getTranslation).collect(Collectors.toList());
.map(user::getTranslation).toList();
case SETTING -> Arrays.asList(active, disabled);
default -> Collections.<String>emptyList();
}).orElse(Collections.emptyList());

View File

@ -1,87 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
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;
public class AdminSwitchtoCommand extends ConfirmableCommand {
private UUID targetUUID;
private @NonNull List<Island> islands;
/**
* Switch player's island to the numbered one in trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminSwitchtoCommand(CompositeCommand parent) {
super(parent, "switchto");
islands = new ArrayList<>();
}
@Override
public void setup() {
setPermission("admin.switchto");
setOnlyPlayer(false);
setParametersHelp("commands.admin.switchto.parameters");
setDescription("commands.admin.switchto.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() != 2) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Check island number
islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (NumberUtils.isDigits(args.get(1))) {
try {
int n = Integer.parseInt(args.get(1));
if (n < 1 || n > islands.size()) {
user.sendMessage("commands.admin.switchto.out-of-range", TextVariables.NUMBER, String.valueOf(islands.size()), TextVariables.LABEL, getTopLabel());
return false;
}
this.askConfirmation(user, () -> {
if (getIslands().switchIsland(getWorld(), targetUUID, islands.get(n -1))) {
user.sendMessage("commands.admin.switchto.success");
} else {
user.sendMessage("commands.admin.switchto.cannot-switch");
}
});
return true;
} catch (Exception e) {
showHelp(this, user);
return false;
}
}
return true;
}
}

View File

@ -1,7 +1,10 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@ -17,11 +20,17 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.util.teleport.SafeSpotTeleport;
/**
* Enables admins to teleport to a player's island, nether or end islands,
*
* For example /acid tp tastybento [island name] would teleport to tastybento's [named] island
*
*/
public class AdminTeleportCommand extends CompositeCommand {
private static final String NOT_SAFE = "general.errors.no-safe-location-found";
private @Nullable UUID targetUUID;
private @Nullable User userToTeleport;
private Location warpSpot;
/**
* @param parent - parent command
@ -41,12 +50,12 @@ public class AdminTeleportCommand extends CompositeCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() != 1 && args.size() != 2) {
if (args.isEmpty()) {
this.showHelp(this, user);
return false;
}
// Check for console or not
if (!user.isPlayer() && args.size() != 2) {
if (!user.isPlayer()) {
user.sendMessage("general.errors.use-in-game");
return false;
}
@ -62,25 +71,6 @@ public class AdminTeleportCommand extends CompositeCommand {
return false;
}
if (args.size() == 2) {
// We are trying to teleport another player
UUID playerToTeleportUUID = Util.getUUID(args.get(1));
if (playerToTeleportUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(1));
return false;
} else {
userToTeleport = User.getInstance(playerToTeleportUUID);
if (!userToTeleport.isOnline()) {
user.sendMessage("general.errors.offline-player");
return false;
}
}
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
World world = getWorld();
if (getLabel().equals("tpnether")) {
world = getPlugin().getIWM().getNetherWorld(getWorld());
@ -91,19 +81,46 @@ public class AdminTeleportCommand extends CompositeCommand {
user.sendMessage(NOT_SAFE);
return false;
}
Location warpSpot = getSpot(world);
// Get default location if there are no arguments
warpSpot = getSpot(world);
if (warpSpot == null) {
user.sendMessage(NOT_SAFE);
return false;
}
if (args.size() == 1) {
return true;
}
// They named the island to go to
Map<String, IslandInfo> names = getNameIslandMap(User.getInstance(targetUUID));
final String name = String.join(" ", args.subList(1, args.size()));
if (!names.containsKey(name)) {
// Failed home name check
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
names.keySet()
.forEach(n -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, n));
return false;
} else if (names.size() > 1) {
IslandInfo info = names.get(name);
Island island = info.island;
warpSpot = island.getSpawnPoint(world.getEnvironment()) != null
? island.getSpawnPoint(world.getEnvironment())
: island.getProtectionCenter().toVector().toLocation(world);
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
Objects.requireNonNull(warpSpot);
// Otherwise, ask the admin to go to a safe spot
String failureMessage = user.getTranslation("commands.admin.tp.manual", "[location]", warpSpot.getBlockX() + " " + warpSpot.getBlockY() + " "
+ warpSpot.getBlockZ());
// Set the player
Player player = args.size() == 2 ? userToTeleport.getPlayer() : user.getPlayer();
Player player = args.size() == 2 ? user.getPlayer() : user.getPlayer();
if (args.size() == 2) {
failureMessage = userToTeleport.getTranslation(NOT_SAFE);
failureMessage = user.getTranslation(NOT_SAFE);
}
// Teleport
@ -118,12 +135,35 @@ public class AdminTeleportCommand extends CompositeCommand {
private Location getSpot(World world) {
Island island = getIslands().getIsland(world, targetUUID);
if (island != null && island.getSpawnPoint(world.getEnvironment()) != null) {
// Return the defined spawn point
return island.getSpawnPoint(world.getEnvironment());
if (island == null) {
return null;
}
// Return the default island protection center
return island.getProtectionCenter().toVector().toLocation(world);
return island.getSpawnPoint(world.getEnvironment()) != null ? island.getSpawnPoint(world.getEnvironment()) : island.getProtectionCenter().toVector().toLocation(world);
}
private record IslandInfo(Island island, boolean islandName) {
}
private Map<String, IslandInfo> getNameIslandMap(User target) {
Map<String, IslandInfo> islandMap = new HashMap<>();
int index = 0;
for (Island island : getIslands().getIslands(getWorld(), target.getUniqueId())) {
index++;
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
islandMap.put(island.getName(), new IslandInfo(island, true));
} else {
// Name has not been set
String text = target.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME,
target.getName(), TextVariables.DISPLAY_NAME, target.getDisplayName()) + " " + index;
islandMap.put(text, new IslandInfo(island, true));
}
// Add homes. Homes do not need an island specified
island.getHomes().keySet().forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
}
return islandMap;
}
@Override
@ -133,8 +173,17 @@ public class AdminTeleportCommand extends CompositeCommand {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
if (args.size() == 2) {
return Optional.of(Util.tabLimit(new ArrayList<>(Util.getOnlinePlayerList(user)), lastArg));
}
if (args.size() == 3) {
UUID target = Util.getUUID(args.get(1));
return target == null ? Optional.empty()
: Optional
.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(User.getInstance(target)).keySet()), lastArg));
}
return Optional.empty();
}
}

View File

@ -0,0 +1,199 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;
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.bentobox.util.teleport.SafeSpotTeleport;
/**
* Enables admins to teleport to a player's island, nether or end islands, or to teleport another player
* to a player's island
*
* For example /acid tp tastybento boxmanager would teleport BoxManager to tastybento's overwold island
*
* If the user has multiple islands, then the format is:
* [admin_command] [user with island] [island to go to]
*/
public class AdminTeleportUserCommand extends CompositeCommand {
private static final String NOT_SAFE = "general.errors.no-safe-location-found";
private @Nullable UUID targetUUID;
private @Nullable User userToTeleport;
/**
* @param parent - parent command
* @param tpCommand - should be "tp", "tpnether" or "tpend"
*/
public AdminTeleportUserCommand(CompositeCommand parent, String tpCommand) {
super(parent, tpCommand);
}
@Override
public void setup() {
// Permission
setPermission("admin.tp");
setParametersHelp("commands.admin.tp.parameters");
setDescription("commands.admin.tp.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.isEmpty() || args.size() > 3) {
this.showHelp(this, user);
return false;
}
// Check for console or not
if (!user.isPlayer() && args.size() == 1) {
user.sendMessage("general.errors.use-in-game");
return false;
}
// Convert name to a UUID
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Check island exists
if (!getIslands().hasIsland(getWorld(), targetUUID) && !getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (args.size() == 2) {
// We are trying to teleport another player
UUID playerToTeleportUUID = Util.getUUID(args.get(1));
if (playerToTeleportUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(1));
return false;
} else {
userToTeleport = User.getInstance(playerToTeleportUUID);
if (!userToTeleport.isOnline()) {
user.sendMessage("general.errors.offline-player");
return false;
}
}
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
World world = getWorld();
if (getLabel().equals("tpnether")) {
world = getPlugin().getIWM().getNetherWorld(getWorld());
} else if (getLabel().equals("tpend")) {
world = getPlugin().getIWM().getEndWorld(getWorld());
}
if (world == null) {
user.sendMessage(NOT_SAFE);
return false;
}
// Get default location if there are no arguments
Location warpSpot = getSpot(world);
if (warpSpot == null) {
user.sendMessage(NOT_SAFE);
return false;
}
// See if there is a quoted island name
if (args.size() == 2) {
Map<String, IslandInfo> names = getNameIslandMap(user);
final String name = String.join(" ", args);
if (!names.containsKey(name)) {
// Failed home name check
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
names.keySet().forEach(
n -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, n));
return false;
} else {
IslandInfo info = names.get(name);
Island island = info.island;
warpSpot = island.getSpawnPoint(world.getEnvironment()) != null
? island.getSpawnPoint(world.getEnvironment())
: island.getProtectionCenter().toVector().toLocation(world);
}
}
// Otherwise, ask the admin to go to a safe spot
String failureMessage = user.getTranslation("commands.admin.tp.manual", "[location]", warpSpot.getBlockX() + " " + warpSpot.getBlockY() + " "
+ warpSpot.getBlockZ());
// Set the player
Player player = args.size() == 2 ? userToTeleport.getPlayer() : user.getPlayer();
if (args.size() == 2) {
failureMessage = userToTeleport.getTranslation(NOT_SAFE);
}
// Teleport
new SafeSpotTeleport.Builder(getPlugin())
.entity(player)
.location(warpSpot)
.failureMessage(failureMessage)
.thenRun(() -> user.sendMessage("general.success"))
.build();
return true;
}
private Location getSpot(World world) {
Island island = getIslands().getIsland(world, targetUUID);
if (island == null) {
return null;
}
return island.getSpawnPoint(world.getEnvironment()) != null ? island.getSpawnPoint(world.getEnvironment()) : island.getProtectionCenter().toVector().toLocation(world);
}
private record IslandInfo(Island island, boolean islandName) {
}
private Map<String, IslandInfo> getNameIslandMap(User user) {
Map<String, IslandInfo> islandMap = new HashMap<>();
int index = 0;
for (Island island : getIslands().getIslands(getWorld(), user.getUniqueId())) {
index++;
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
islandMap.put(island.getName(), new IslandInfo(island, true));
} else {
// Name has not been set
String text = user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index;
islandMap.put(text, new IslandInfo(island, true));
}
// Add homes. Homes do not need an island specified
island.getHomes().keySet().forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
}
return islandMap;
}
@Override
public Optional<List<String>> tabComplete(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();
}
if (args.size() == 1) {
return Optional.of(Util.tabLimit(new ArrayList<>(Util.getOnlinePlayerList(user)), lastArg));
}
if (args.size() == 2) {
return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(user).keySet()), lastArg));
}
return Optional.empty();
}
}

View File

@ -1,73 +0,0 @@
package world.bentobox.bentobox.api.commands.admin;
import java.util.List;
import java.util.UUID;
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.IslandInfo;
public class AdminTrashCommand extends CompositeCommand {
/**
* A command for viewing islands in the database trash
* @param parent - admin command
* @since 1.3.0
*/
public AdminTrashCommand(CompositeCommand parent) {
super(parent, "trash");
}
@Override
public void setup() {
setPermission("admin.trash");
setOnlyPlayer(false);
setParametersHelp("commands.admin.trash.parameters");
setDescription("commands.admin.trash.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() > 1) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
UUID targetUUID = args.isEmpty() ? null : getPlayers().getUUID(args.get(0));
if (!args.isEmpty() && targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Show trash can info for this player
List<Island> islands = getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID);
if (islands.isEmpty()) {
if (args.isEmpty()) {
user.sendMessage("commands.admin.trash.no-unowned-in-trash");
} else {
user.sendMessage("commands.admin.trash.no-islands-in-trash");
}
return false;
} else {
if (targetUUID == null) {
showTrash(user, islands);
} else {
getIslands().getQuarantineCache().values().forEach(v -> showTrash(user, v));
}
return true;
}
}
private void showTrash(User user, List<Island> islands) {
user.sendMessage("commands.admin.trash.title");
for (int i = 0; i < islands.size(); i++) {
user.sendMessage("commands.admin.trash.count", TextVariables.NUMBER, String.valueOf(i+1));
new IslandInfo(islands.get(i)).showInfo(user);
}
user.sendMessage("commands.admin.trash.use-switch", TextVariables.LABEL, getTopLabel());
user.sendMessage("commands.admin.trash.use-emptytrash", TextVariables.LABEL, getTopLabel());
}
}

View File

@ -2,13 +2,19 @@ package world.bentobox.bentobox.api.commands.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
@ -16,6 +22,9 @@ import world.bentobox.bentobox.util.Util;
public class AdminUnregisterCommand extends ConfirmableCommand {
private Island targetIsland;
private @Nullable UUID targetUUID;
public AdminUnregisterCommand(CompositeCommand parent) {
super(parent, "unregister");
}
@ -30,12 +39,12 @@ public class AdminUnregisterCommand extends ConfirmableCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
if (args.isEmpty()) {
showHelp(this, user);
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
@ -44,43 +53,71 @@ public class AdminUnregisterCommand extends ConfirmableCommand {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
// Check if the player has more than one island
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.size() == 0) {
user.sendMessage("general.errors.player-has-no-island");
return false;
} else if (args.size() == 1) {
if (islands.size() == 1) {
targetIsland = islands.values().iterator().next();
return true;
} else {
// They need to specify which island
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
}
} else if (args.size() != 2) {
// Check if the name given works
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
} else if (!islands.containsKey(args.get(1))) {
if (args.get(1).equalsIgnoreCase("help")) {
this.showHelp(this, user);
return false;
}
user.sendMessage("commands.admin.unregister.errors.unknown-island-location");
return false;
}
targetIsland = islands.get(args.get(1));
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Get target
UUID targetUUID = getPlayers().getUUID(args.get(0));
Objects.requireNonNull(targetIsland);
Objects.requireNonNull(targetUUID);
// Everything's fine, we can set the island as spawn :)
askConfirmation(user, () -> unregisterPlayer(user, args.get(0), targetUUID));
askConfirmation(user, () -> unregisterIsland(user));
return true;
}
void unregisterPlayer(User user, String targetName, UUID targetUUID) {
void unregisterIsland(User user) {
// Unregister island
Island oldIsland = getIslands().getIsland(getWorld(), targetUUID);
if (oldIsland == null) return;
IslandEvent.builder()
.island(oldIsland)
.location(oldIsland.getCenter())
.island(targetIsland)
.location(targetIsland.getCenter())
.reason(IslandEvent.Reason.UNREGISTERED)
.involvedPlayer(targetUUID)
.admin(true)
.build();
IslandEvent.builder()
.island(oldIsland)
.island(targetIsland)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.OWNER_RANK, RanksManager.VISITOR_RANK)
.build();
targetIsland.setOwner(null);
// Remove all island members
oldIsland.getMemberSet().forEach(m -> getIslands().removePlayer(getWorld(), m));
targetIsland.getMemberSet().forEach(m -> getIslands().removePlayer(targetIsland, m));
// Remove all island players that reference this island
oldIsland.getMembers().clear();
getIslands().save(oldIsland);
user.sendMessage("commands.admin.unregister.unregistered-island", TextVariables.XYZ, Util.xyz(oldIsland.getCenter().toVector()),
TextVariables.NAME, targetName);
targetIsland.getMembers().clear();
targetIsland.log(new LogEntry.Builder("UNREGISTER").data("player", targetUUID.toString())
.data("admin", user.getUniqueId().toString()).build());
user.sendMessage("commands.admin.unregister.unregistered-island", TextVariables.XYZ, Util.xyz(targetIsland.getCenter().toVector()),
TextVariables.NAME, getPlayers().getName(targetUUID));
}
@Override
@ -89,8 +126,22 @@ public class AdminUnregisterCommand extends ConfirmableCommand {
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
} else if (args.size() == 2) {
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
} else if (args.size() > 2) {
// Find out which user
UUID uuid = getPlayers().getUUID(args.get(1));
if (uuid != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(getIslandsXYZ(uuid).keySet()), lastArg));
}
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
return Optional.empty();
}
private Map<String, Island> getIslandsXYZ(UUID target) {
return getIslands().getOwnedIslands(getWorld(), target).stream()
.collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island));
}
}

View File

@ -10,8 +10,8 @@ import world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand;
import world.bentobox.bentobox.api.commands.admin.range.AdminRangeCommand;
import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamFixCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamSetownerCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -29,8 +29,9 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
*
* @param addon - GameMode addon
*/
public DefaultAdminCommand(GameModeAddon addon) {
protected DefaultAdminCommand(GameModeAddon addon) {
// Register command with alias from config.
// The first command listed is the "label" and the others are aliases.
super(addon,
addon.getWorldSettings().getAdminCommandAliases().split(" ")[0],
addon.getWorldSettings().getAdminCommandAliases().split(" "));
@ -42,7 +43,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
*/
@Override
public void setup() {
this.setPermission("admin.*");
this.setPermission("admin");
this.setOnlyPlayer(false);
this.setParametersHelp("commands.admin.help.parameters");
@ -56,11 +57,11 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminSetrankCommand(this);
new AdminInfoCommand(this);
// Team commands
new AdminTeamCommand(this);
new AdminTeamAddCommand(this);
new AdminTeamKickCommand(this);
new AdminTeamDisbandCommand(this);
new AdminTeamSetownerCommand(this);
new AdminTeamFixCommand(this);
// Blueprints
new AdminBlueprintCommand(this);
// Register/unregister islands
@ -94,6 +95,8 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminSetProtectionCenterCommand(this);
// Delete homes
new AdminDeleteHomesCommand(this);
// Reset name
new AdminResetNameCommand(this);
}
/**

View File

@ -12,6 +12,7 @@ import org.bukkit.Particle;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.commands.admin.range.AdminRangeDisplayCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintsManager;
@ -23,7 +24,6 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
// Map containing selection cuboid display tasks
private Map<User, Integer> displayClipboards;
private static final Particle PARTICLE = Particle.REDSTONE;
private static final Particle.DustOptions PARTICLE_DUST_OPTIONS = new Particle.DustOptions(Color.RED, 1.0F);
public AdminBlueprintCommand(CompositeCommand parent) {
@ -62,20 +62,30 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
return clipboards;
}
protected void showClipboard(User user) {
displayClipboards.putIfAbsent(user, Bukkit.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
if (!user.isPlayer() || !user.getPlayer().isOnline()) {
hideClipboard(user);
}
if (clipboards.containsKey(user.getUniqueId())) {
BlueprintClipboard clipboard = clipboards.get(user.getUniqueId());
paintAxis(user, clipboard);
}
/**
* This method shows clipboard for requested user.
* @param user User who need to see clipboard.
*/
protected void showClipboard(User user)
{
this.displayClipboards.computeIfAbsent(user,
key -> Bukkit.getScheduler().scheduleSyncRepeatingTask(this.getPlugin(), () ->
{
if (!key.isPlayer() || !key.getPlayer().isOnline())
{
this.hideClipboard(key);
}
}, 20, 20));
if (this.clipboards.containsKey(key.getUniqueId()))
{
BlueprintClipboard clipboard = this.clipboards.get(key.getUniqueId());
this.paintAxis(key, clipboard);
}
}, 20, 20));
}
private void paintAxis(User user, BlueprintClipboard clipboard) {
if (clipboard.getPos1() == null || clipboard.getPos2() == null) {
return;
@ -89,31 +99,38 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
// Drawing x-axes
for (int x = minX; x <= maxX; x++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5, maxZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5, maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, minY + 0.5,
maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, x + 0.5, maxY + 0.5,
maxZ + 0.5);
}
// Drawing y-axes
for (int y = minY; y <= maxY; y++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5, minZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5, maxZ + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5, maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5,
minZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, y + 0.5,
maxZ + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, y + 0.5,
maxZ + 0.5);
}
// Drawing z-axes
for (int z = minZ; z <= maxZ; z++) {
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, minY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, minY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, maxY + 0.5, z + 0.5);
user.spawnParticle(PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, maxY + 0.5, z + 0.5);
}
// Drawing origin
if (clipboard.getOrigin() != null) {
user.spawnParticle(Particle.VILLAGER_HAPPY, null, clipboard.getOrigin().getBlockX() + 0.5, clipboard.getOrigin().getBlockY() + 0.5, clipboard.getOrigin().getBlockZ() + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, minY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, minY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, minX + 0.5, maxY + 0.5,
z + 0.5);
user.spawnParticle(AdminRangeDisplayCommand.PARTICLE, PARTICLE_DUST_OPTIONS, maxX + 0.5, maxY + 0.5,
z + 0.5);
}
}

View File

@ -1,35 +1,54 @@
package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.util.List;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
public class AdminBlueprintCopyCommand extends CompositeCommand {
public AdminBlueprintCopyCommand(AdminBlueprintCommand parent) {
public class AdminBlueprintCopyCommand extends CompositeCommand
{
public AdminBlueprintCopyCommand(AdminBlueprintCommand parent)
{
super(parent, "copy");
}
@Override
public void setup() {
inheritPermission();
public void setup()
{
setPermission("admin.blueprint.copy");
setParametersHelp("commands.admin.blueprint.copy.parameters");
setDescription("commands.admin.blueprint.copy.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() > 1) {
showHelp(this, user);
public boolean execute(User user, String label, List<String> args)
{
if (args.size() > 2)
{
this.showHelp(this, user);
return false;
}
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
boolean copyAir = (args.size() == 1 && args.get(0).equalsIgnoreCase("air"));
return clipboard.copy(user, copyAir);
BlueprintClipboard clipboard =
parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
boolean copyAir = args.stream().anyMatch(key -> key.equalsIgnoreCase("air"));
boolean copyBiome = args.stream().anyMatch(key -> key.equalsIgnoreCase("biome"));
return clipboard.copy(user, copyAir, copyBiome);
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args)
{
return Optional.of(List.of("air", "biome"));
}
}

View File

@ -2,55 +2,68 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
/**
* Command that deletes a Blueprint.
* @author Poslovitch
* @since 1.9.0
*/
public class AdminBlueprintDeleteCommand extends ConfirmableCommand {
public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) {
public class AdminBlueprintDeleteCommand extends ConfirmableCommand
{
public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent)
{
super(parent, "delete", "remove");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.delete.parameters");
setDescription("commands.admin.blueprint.delete.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 1) {
showHelp(this, user);
public void setup()
{
setPermission("admin.blueprint.delete");
this.setParametersHelp("commands.admin.blueprint.delete.parameters");
this.setDescription("commands.admin.blueprint.delete.description");
}
@Override
public boolean execute(User user, String label, List<String> args)
{
if (args.size() != 1)
{
this.showHelp(this, user);
return false;
}
String blueprintName = args.get(0).toLowerCase(Locale.ENGLISH);
String blueprintName = Util.sanitizeInput(args.get(0));
// Check if blueprint exist
if (getPlugin().getBlueprintsManager().getBlueprints(getAddon()).containsKey(blueprintName)) {
askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), () -> {
getPlugin().getBlueprintsManager().deleteBlueprint(getAddon(), blueprintName);
user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName);
});
if (this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).containsKey(blueprintName))
{
this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"),
() -> {
this.getPlugin().getBlueprintsManager().deleteBlueprint(this.getAddon(), blueprintName);
user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName);
});
return true;
} else {
}
else
{
user.sendMessage("commands.admin.blueprint.delete.no-blueprint", TextVariables.NAME, blueprintName);
return false;
}
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
return Optional.of(new LinkedList<>(getPlugin().getBlueprintsManager().getBlueprints(getAddon()).keySet()));
public Optional<List<String>> tabComplete(User user, String alias, List<String> args)
{
return Optional.of(new LinkedList<>(this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).keySet()));
}
}
}

View File

@ -5,51 +5,66 @@ import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.managers.BlueprintsManager;
public class AdminBlueprintListCommand extends CompositeCommand {
public class AdminBlueprintListCommand extends CompositeCommand
{
public AdminBlueprintListCommand(AdminBlueprintCommand parent) {
public AdminBlueprintListCommand(AdminBlueprintCommand parent)
{
super(parent, "list");
}
@Override
public void setup() {
inheritPermission();
setDescription("commands.admin.blueprint.list.description");
public void setup()
{
setPermission("admin.blueprint.list");
this.setDescription("commands.admin.blueprint.list.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (!args.isEmpty()) {
showHelp(this, user);
public boolean canExecute(User user, String label, List<String> args)
{
if (!args.isEmpty())
{
this.showHelp(this, user);
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
File blueprints = new File(getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME);
if (!blueprints.exists()) {
public boolean execute(User user, String label, List<String> args)
{
File blueprints = new File(this.getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME);
if (!blueprints.exists())
{
user.sendMessage("commands.admin.blueprint.list.no-blueprints");
return false;
}
FilenameFilter blueprintFilter = (File dir, String name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
List<String> blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).collect(Collectors.toList());
if (blueprintList.isEmpty()) {
FilenameFilter blueprintFilter = (File dir, String name) -> name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
List<String> blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).
map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).
toList();
if (blueprintList.isEmpty())
{
user.sendMessage("commands.admin.blueprint.list.no-blueprints");
return false;
}
user.sendMessage("commands.admin.blueprint.list.available-blueprints");
blueprintList.forEach(user::sendRawMessage);
return true;
}
}

View File

@ -17,7 +17,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.blueprint.load");
setParametersHelp("commands.admin.blueprint.load.parameters");
setDescription("commands.admin.blueprint.load.description");
}
@ -32,7 +32,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand {
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboardManager bp = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder());
if (bp.load(user, args.get(0))) {
if (bp.load(user, Util.sanitizeInput(args.get(0)))) {
parent.getClipboards().put(user.getUniqueId(), bp.getClipboard());
return true;
}

View File

@ -19,7 +19,7 @@ public class AdminBlueprintOriginCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.blueprint.origin");
setParametersHelp("commands.admin.blueprint.origin.parameters");
setDescription("commands.admin.blueprint.origin.description");
}

View File

@ -15,7 +15,7 @@ public class AdminBlueprintPasteCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.blueprint.paste");
setParametersHelp("commands.admin.blueprint.paste.parameters");
setDescription("commands.admin.blueprint.paste.description");
}
@ -25,7 +25,7 @@ public class AdminBlueprintPasteCommand extends CompositeCommand {
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
if (clipboard.isFull()) {
new BlueprintPaster(getPlugin(), clipboard, user.getLocation()).paste().thenAccept(b -> {
new BlueprintPaster(getPlugin(), clipboard, user.getLocation()).paste(false).thenAccept(b -> {
user.sendMessage("general.success");
parent.showClipboard(user);
});

View File

@ -15,7 +15,7 @@ public class AdminBlueprintPos1Command extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.blueprint.pos1");
setParametersHelp("commands.admin.blueprint.pos1.parameters");
setDescription("commands.admin.blueprint.pos1.description");
}

View File

@ -15,7 +15,7 @@ public class AdminBlueprintPos2Command extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.blueprint.pos2");
setParametersHelp("commands.admin.blueprint.pos2.parameters");
setDescription("commands.admin.blueprint.pos2.description");
}

View File

@ -2,72 +2,111 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.io.File;
import java.util.List;
import java.util.Locale;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
/**
* Renames an existing blueprint.
* @author Poslovitch
* @since 1.10.0
*/
public class AdminBlueprintRenameCommand extends ConfirmableCommand {
public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) {
public class AdminBlueprintRenameCommand extends ConfirmableCommand
{
public AdminBlueprintRenameCommand(AdminBlueprintCommand parent)
{
super(parent, "rename");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.rename.parameters");
setDescription("commands.admin.blueprint.rename.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
public void setup()
{
setPermission("admin.blueprint.rename");
this.setParametersHelp("commands.admin.blueprint.rename.parameters");
this.setDescription("commands.admin.blueprint.rename.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args)
{
if (args.size() != 2)
{
// Blueprint must have a name.
this.showHelp(this, user);
return false;
}
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
String from = Util.sanitizeInput(args.get(0));
String to = Util.sanitizeInput(args.get(1));
// Check if the names are the same
String from = args.get(0).toLowerCase(Locale.ENGLISH);
String to = args.get(1).toLowerCase(Locale.ENGLISH);
if (from.equals(to)) {
// Check if name is changed.
if (from.equals(to))
{
user.sendMessage("commands.admin.blueprint.rename.pick-different-name");
return false;
}
// Check if the 'from' file exists
AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent();
File fromFile = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.BLUEPRINT_SUFFIX);
if (!fromFile.exists()) {
if (!fromFile.exists())
{
user.sendMessage("commands.admin.blueprint.no-such-file");
return false;
}
// Check if the 'to' file exists
File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX);
if (toFile.exists()) {
// Ask for confirmation to overwrite
askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> rename(user, from, to));
} else {
askConfirmation(user, () -> rename(user, from, to));
}
return true;
}
private void rename(User user, String blueprintName, String newName) {
Blueprint blueprint = getPlugin().getBlueprintsManager().getBlueprints(getAddon()).get(blueprintName);
getPlugin().getBlueprintsManager().renameBlueprint(getAddon(), blueprint, newName);
user.sendMessage("commands.admin.blueprint.rename.success", "[old]", blueprintName, TextVariables.NAME, blueprint.getName());
@Override
public boolean execute(User user, String label, List<String> args)
{
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
// Check if the names are the same
String from = Util.sanitizeInput(args.get(0));
String to = Util.sanitizeInput(args.get(1));
// Check if the 'to' file exists
File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX);
if (toFile.exists())
{
// Ask for confirmation to overwrite
this.askConfirmation(user,
user.getTranslation("commands.admin.blueprint.file-exists"),
() -> this.rename(user, from, to, args.get(1)));
}
else
{
this.askConfirmation(user, () -> this.rename(user, from, to, args.get(1)));
}
return true;
}
private void rename(User user, String blueprintName, String fileName, String displayName)
{
Blueprint blueprint = this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).get(blueprintName);
this.getPlugin().getBlueprintsManager().renameBlueprint(this.getAddon(), blueprint, fileName, displayName);
user.sendMessage("commands.admin.blueprint.rename.success",
"[old]",
blueprintName,
TextVariables.NAME,
blueprint.getName(),
"[display]",
blueprint.getDisplayName());
}
}

View File

@ -2,62 +2,115 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.io.File;
import java.util.List;
import java.util.Locale;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintClipboardManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
public class AdminBlueprintSaveCommand extends ConfirmableCommand {
public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) {
/**
* This method allows to save blueprint from the clipboard.
*/
public class AdminBlueprintSaveCommand extends ConfirmableCommand
{
public AdminBlueprintSaveCommand(AdminBlueprintCommand parent)
{
super(parent, "save");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.save.parameters");
setDescription("commands.admin.blueprint.save.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 1) {
showHelp(this, user);
public void setup()
{
setPermission("admin.blueprint.save");
this.setParametersHelp("commands.admin.blueprint.save.parameters");
this.setDescription("commands.admin.blueprint.save.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args)
{
if (args.size() != 1)
{
// Blueprint must have a name.
this.showHelp(this, user);
return false;
}
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
String fileName = args.get(0).toLowerCase(Locale.ENGLISH);
if (clipboard.isFull()) {
// Check if blueprint had bedrock
if (clipboard.getBlueprint().getBedrock() == null) {
user.sendMessage("commands.admin.blueprint.bedrock-required");
return false;
}
// Check if file exists
File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (newFile.exists()) {
this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> hideAndSave(user, parent, clipboard, fileName));
return false;
}
return hideAndSave(user, parent, clipboard, fileName);
} else {
BlueprintClipboard clipboard = ((AdminBlueprintCommand) this.getParent()).getClipboards().
computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
if (!clipboard.isFull())
{
// Clipboard is not set up.
user.sendMessage("commands.admin.blueprint.copy-first");
return false;
}
if (clipboard.getBlueprint() != null && clipboard.getBlueprint().getBedrock() == null)
{
// Bedrock is required for all blueprints.
user.sendMessage("commands.admin.blueprint.bedrock-required");
return false;
}
return true;
}
private boolean hideAndSave(User user, AdminBlueprintCommand parent, BlueprintClipboard clipboard, String name) {
parent.hideClipboard(user);
boolean result = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder(), clipboard).save(user, name);
if (result && clipboard.getBlueprint() != null) {
getPlugin().getBlueprintsManager().addBlueprint(getAddon(), clipboard.getBlueprint());
@Override
public boolean execute(User user, String label, List<String> args)
{
AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent();
BlueprintClipboard clipboard = parent.getClipboards().
computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
String fileName = Util.sanitizeInput(args.get(0));
// Check if file exists
File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (newFile.exists())
{
this.askConfirmation(user,
user.getTranslation("commands.admin.blueprint.file-exists"),
() -> this.hideAndSave(user, parent, clipboard, fileName, args.get(0)));
return false;
}
return this.hideAndSave(user, parent, clipboard, fileName, args.get(0));
}
/**
* This method saves given blueprint.
* @param user User that triggers blueprint save.
* @param parent Parent command that contains clipboard.
* @param clipboard Active clipboard.
* @param name Filename for the blueprint
* @param displayName Display name for the blueprint.
* @return {@code true} if blueprint is saved, {@code false} otherwise.
*/
private boolean hideAndSave(User user,
AdminBlueprintCommand parent,
BlueprintClipboard clipboard,
String name,
String displayName)
{
parent.hideClipboard(user);
boolean result = new BlueprintClipboardManager(this.getPlugin(),
parent.getBlueprintsFolder(), clipboard).
save(user, name, displayName);
if (result && clipboard.isFull())
{
this.getPlugin().getBlueprintsManager().addBlueprint(this.getAddon(), clipboard.getBlueprint());
}
return result;
}
}

View File

@ -17,8 +17,10 @@ import world.bentobox.bentobox.database.objects.Island;
*/
public class NamePrompt extends StringPrompt {
private @NonNull final Island island;
private @NonNull final User user;
@NonNull
private final Island island;
@NonNull
private final User user;
private final String oldName;
private final BentoBox plugin;
@ -30,14 +32,14 @@ public class NamePrompt extends StringPrompt {
}
@Override
public @NonNull String getPromptText(@NonNull ConversationContext context) {
@NonNull
public String getPromptText(@NonNull ConversationContext context) {
return user.getTranslation("commands.island.renamehome.enter-new-name");
}
@Override
public Prompt acceptInput(@NonNull ConversationContext context, String input) {
if (island.renameHome(oldName, input)) {
plugin.getIslands().save(island);
Bukkit.getScheduler().runTask(plugin, () -> user.sendMessage("general.success"));
} else {
Bukkit.getScheduler().runTask(plugin, () -> user.sendMessage("commands.island.renamehome.already-exists"));

View File

@ -22,7 +22,7 @@ public class AdminDeathsAddCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.deaths.add");
setDescription("commands.admin.deaths.add.description");
setParametersHelp("commands.admin.deaths.add.parameters");
}

View File

@ -22,7 +22,7 @@ public class AdminDeathsRemoveCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.deaths.remove");
setDescription("commands.admin.deaths.remove.description");
setParametersHelp("commands.admin.deaths.remove.parameters");
}

View File

@ -21,7 +21,7 @@ public class AdminDeathsResetCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.deaths.reset");
setDescription("commands.admin.deaths.reset.description");
setParametersHelp("commands.admin.deaths.reset.parameters");
}

View File

@ -21,7 +21,7 @@ public class AdminDeathsSetCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.deaths.set");
setDescription("commands.admin.deaths.set.description");
setParametersHelp("commands.admin.deaths.set.parameters");
}

View File

@ -5,7 +5,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
@ -18,6 +17,7 @@ import world.bentobox.bentobox.api.events.island.IslandDeletedEvent;
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;
public class AdminPurgeCommand extends CompositeCommand implements Listener {
@ -82,7 +82,7 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener {
user.sendMessage("commands.admin.purge.confirm", TextVariables.LABEL, this.getTopLabel());
return false;
}
} catch(Exception e) {
} catch (NumberFormatException e) {
user.sendMessage("commands.admin.purge.number-error");
return false;
}
@ -120,29 +120,42 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener {
}
}
/**
* Gets a set of islands that are older than the parameter in days
* @param days days
* @return set of islands
*/
Set<String> getOldIslands(int days) {
long currentTimeMillis = System.currentTimeMillis();
double daysInMilliseconds = days * 1000 * 3600 * 24;
Set<String> oldIslands = new HashSet<>();
// Process islands in one pass, logging and adding to the set if applicable
getPlugin().getIslands().getIslands().stream()
.filter(i -> !i.isSpawn())
.filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld()))
.filter(Island::isOwned)
.filter(i -> i.getMembers().size() == 1)
.filter(i -> ((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) > days)
.forEach(i -> {
Date date = new Date(Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed());
BentoBox.getInstance().log("Will purge " +
BentoBox.getInstance().getPlayers().getName(i.getOwner()) +
" last logged in " + (int)((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) + " days ago. " + date);
});
return getPlugin().getIslands().getIslands().stream()
.filter(i -> !i.isSpawn())
.filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld()))
.filter(Island::isOwned)
.filter(i -> i.getMembers().size() == 1)
.filter(i -> ((double)(System.currentTimeMillis() - Bukkit.getOfflinePlayer(i.getOwner()).getLastPlayed()) / 1000 / 3600 / 24) > days)
.map(Island::getUniqueId)
.collect(Collectors.toSet());
.filter(i -> !i.isSpawn()).filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld().equals(this.getWorld())).filter(Island::isOwned).filter(
i -> i.getMemberSet().stream()
.allMatch(member -> (currentTimeMillis
- Bukkit.getOfflinePlayer(member).getLastPlayed()) > daysInMilliseconds))
.forEach(i -> {
// Add the unique island ID to the set
oldIslands.add(i.getUniqueId());
BentoBox.getInstance().log("Will purge island at " + Util.xyz(i.getCenter().toVector()) + " in "
+ i.getWorld().getName());
// Log each member's last login information
i.getMemberSet().forEach(member -> {
Date lastLogin = new Date(Bukkit.getOfflinePlayer(member).getLastPlayed());
BentoBox.getInstance()
.log("Player " + BentoBox.getInstance().getPlayers().getName(member)
+ " last logged in "
+ (int) ((currentTimeMillis - Bukkit.getOfflinePlayer(member).getLastPlayed())
/ 1000 / 3600 / 24)
+ " days ago. " + lastLogin);
});
BentoBox.getInstance().log("+-----------------------------------------+");
});
return oldIslands;
}
/**

View File

@ -16,7 +16,7 @@ public class AdminPurgeProtectCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.purge.protect");
setOnlyPlayer(true);
setDescription("commands.admin.purge.protect.description");
}

View File

@ -19,7 +19,7 @@ public class AdminPurgeStatusCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.purge.status");
setOnlyPlayer(false);
setParametersHelp("commands.admin.purge.status.parameters");
setDescription("commands.admin.purge.status.description");

View File

@ -13,7 +13,7 @@ public class AdminPurgeStopCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.purge.stop");
setOnlyPlayer(false);
setDescription("commands.admin.purge.stop.description");
}

View File

@ -17,7 +17,7 @@ public class AdminPurgeUnownedCommand extends ConfirmableCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.purge.unowned");
setOnlyPlayer(false);
setParametersHelp("commands.admin.purge.unowned.parameters");
setDescription("commands.admin.purge.unowned.description");

View File

@ -0,0 +1,103 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
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;
/**
* @author Poslovitch
*/
public abstract class AbstractAdminRangeCommand extends CompositeCommand {
protected @Nullable UUID targetUUID;
protected Island targetIsland;
public AbstractAdminRangeCommand(CompositeCommand parent, String string) {
super(parent, string);
}
@Override
public boolean canExecute(User user, String label, @NonNull List<String> args) {
if (args.size() <= 1) {
showHelp(this, user);
return false;
}
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
// Check if the player has more than one island
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.size() == 0) {
user.sendMessage("general.errors.player-has-no-island");
return false;
} else if (args.size() == 2) {
// If they only have one island, 2 args are fine
if (islands.size() == 1) {
targetIsland = islands.values().iterator().next();
return true;
} else {
// They need to specify which island
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
}
} else if (args.size() != 3) {
// No location
user.sendMessage("commands.admin.unregister.errors.specify-island-location");
return false;
} else if (!islands.containsKey(args.get(2))) {
if (args.get(2).equalsIgnoreCase("help")) {
this.showHelp(this, user);
return false;
}
user.sendMessage("commands.admin.unregister.errors.unknown-island-location");
return false;
}
targetIsland = islands.get(args.get(2));
return true;
}
protected Map<String, Island> getIslandsXYZ(UUID target) {
return getIslands().getOwnedIslands(getWorld(), target).stream()
.collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island));
}
@Override
public Optional<List<String>> tabComplete(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() == 3) {
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
} else if (args.size() > 4) {
// Find out which user
UUID uuid = getPlayers().getUUID(args.get(2));
if (uuid != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(getIslandsXYZ(uuid).keySet()), lastArg));
}
}
return Optional.empty();
}
}

View File

@ -1,23 +1,18 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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;
/**
* @since 1.10.0
* @author Poslovitch
*/
public class AdminRangeAddCommand extends CompositeCommand {
public class AdminRangeAddCommand extends AbstractAdminRangeCommand {
public AdminRangeAddCommand(AdminRangeCommand parent) {
super(parent, "add");
@ -25,60 +20,35 @@ public class AdminRangeAddCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.range.add");
setDescription("commands.admin.range.add.description");
setParametersHelp("commands.admin.range.add.parameters");
}
@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
return false;
}
int newRange = targetIsland.getProtectionRange() + Integer.parseInt(args.get(1));
UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
if (newRange > targetIsland.getRange()) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER,
String.valueOf(targetIsland.getRange()));
return false;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
int newRange = island.getProtectionRange() + Integer.parseInt(args.get(1));
if (newRange > island.getRange()) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER, String.valueOf(island.getRange()));
return false;
} else if (newRange == island.getProtectionRange()) {
} else if (newRange == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(newRange);
targetIsland.setProtectionRange(newRange);
// Call Protection Range Change event. Does not support cancelling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)
.protectionRange(newRange, oldRange)
.build();
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE).involvedPlayer(targetUUID).admin(true)
.protectionRange(newRange, oldRange).build();
user.sendMessage("commands.admin.range.add.success",
TextVariables.NAME, args.get(0), TextVariables.NUMBER, args.get(1),
@ -86,4 +56,6 @@ public class AdminRangeAddCommand extends CompositeCommand {
return true;
}
}

View File

@ -31,4 +31,5 @@ public class AdminRangeCommand extends CompositeCommand {
showHelp(this, user);
return true;
}
}
}

View File

@ -7,13 +7,13 @@ import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import com.google.common.base.Enums;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
/**
* @author Poslovitch
@ -24,8 +24,9 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
private static final String DISPLAY = "display";
private static final String SHOW = "show";
private static final String HIDE = "hide";
// Since 1.18, the Particle.BARRIER was replaced by BLOCK_MARKER
private static final Particle BARRIER = Enums.getIfPresent(Particle.class, "BARRIER").or(Enums.getIfPresent(Particle.class, "BLOCK_MARKER").or(Particle.LAVA));
public static final Particle PARTICLE = Util.findFirstMatchingEnum(Particle.class, "REDSTONE", "DUST");
private static final Particle PARTICLE2 = Util.findFirstMatchingEnum(Particle.class, "VILLAGER_HAPPY",
"HAPPY_VILLAGER");
// Map of users to which ranges must be displayed
private final Map<User, Integer> displayRanges = new HashMap<>();
@ -75,15 +76,15 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
getIslands().getIslandAt(user.getLocation()).ifPresent(island -> {
// Draw the island protected area
drawZone(user, BARRIER, null, island, island.getProtectionRange());
drawZone(user, Particle.BLOCK_MARKER, Material.BARRIER.createBlockData(), island, island.getProtectionRange());
// Draw the default protected area if island protected zone is different
if (island.getProtectionRange() != getPlugin().getIWM().getIslandProtectionRange(getWorld())) {
drawZone(user, Particle.VILLAGER_HAPPY, null, island, getPlugin().getIWM().getIslandProtectionRange(getWorld()));
drawZone(user, PARTICLE2, null, island, getPlugin().getIWM().getIslandProtectionRange(getWorld()));
}
// Draw the island area
drawZone(user, Particle.REDSTONE, new Particle.DustOptions(Color.GRAY, 1.0F), island, island.getRange());
drawZone(user, PARTICLE, new Particle.DustOptions(Color.GRAY, 1.0F), island, island.getRange());
});
}, 20, 30));
}
@ -94,7 +95,7 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
displayRanges.remove(user);
}
private void drawZone(User user, Particle particle, Particle.DustOptions dustOptions, Island island, int range) {
private void drawZone(User user, Particle particle, Object dustOptions, Island island, int range) {
Location center = island.getProtectionCenter();
// Get player Y coordinate
int playerY = user.getPlayer().getLocation().getBlockY() + 1;
@ -102,10 +103,10 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
// Draw 3 "stages" (one line below, at and above player's y coordinate)
for (int stage = -1 ; stage <= 1 ; stage++) {
for (int i = -range ; i <= range ; i++) {
user.spawnParticle(particle, dustOptions, center.getBlockX() + i, playerY + stage, center.getBlockZ() + range);
user.spawnParticle(particle, dustOptions, center.getBlockX() + i, playerY + stage, center.getBlockZ() - range);
user.spawnParticle(particle, dustOptions, center.getBlockX() + range, playerY + stage, center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, center.getBlockX() - range, playerY + stage, center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + i, (double)playerY + stage, (double)center.getBlockZ() + range);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + i, (double)playerY + stage, (double)center.getBlockZ() - range);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() + range, (double)playerY + stage, (double)center.getBlockZ() + i);
user.spawnParticle(particle, dustOptions, (double)center.getBlockX() - range, (double)playerY + stage, (double)center.getBlockZ() + i);
}
}
}

View File

@ -1,23 +1,19 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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;
/**
* @since 1.10.0
* @author Poslovitch
*/
public class AdminRangeRemoveCommand extends CompositeCommand {
public class AdminRangeRemoveCommand extends AbstractAdminRangeCommand {
public AdminRangeRemoveCommand(AdminRangeCommand parent) {
super(parent, "remove");
@ -25,55 +21,38 @@ public class AdminRangeRemoveCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.range.remove");
setDescription("commands.admin.range.remove.description");
setParametersHelp("commands.admin.range.remove.parameters");
}
@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
return false;
}
UUID targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
return false;
}
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
int newRange = island.getProtectionRange() - Integer.parseInt(args.get(1));
int newRange = targetIsland.getProtectionRange() - Integer.parseInt(args.get(1));
if (newRange <= 1) {
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER, String.valueOf(island.getRange()));
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER,
String.valueOf(targetIsland.getRange()));
return false;
} else if (newRange == island.getProtectionRange()) {
} else if (newRange == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(newRange);
targetIsland.setProtectionRange(newRange);
// Call Protection Range Change event. Does not support cancelling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)

View File

@ -1,19 +1,14 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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;
public class AdminRangeSetCommand extends CompositeCommand {
public class AdminRangeSetCommand extends AbstractAdminRangeCommand {
public AdminRangeSetCommand(CompositeCommand parent) {
super(parent, "set");
@ -28,23 +23,6 @@ public class AdminRangeSetCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 2) {
// Show help
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;
}
if (!(getIslands().hasIsland(getWorld(), targetUUID) || getIslands().inTeam(getWorld(), targetUUID))) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
// Get new range
if (!Util.isInteger(args.get(1), true) || Integer.parseInt(args.get(1)) < 0) {
user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(1));
@ -52,33 +30,30 @@ public class AdminRangeSetCommand extends CompositeCommand {
}
int range = Integer.parseInt(args.get(1));
// Get island
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
// Do some sanity checks to make sure the new protection range won't cause problems
if (range < 1) {
user.sendMessage("commands.admin.range.invalid-value.too-low", TextVariables.NUMBER, args.get(1));
return false;
}
if (range > island.getRange() * 2) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER, String.valueOf(2 * island.getRange()));
if (range > targetIsland.getRange() * 2) {
user.sendMessage("commands.admin.range.invalid-value.too-high", TextVariables.NUMBER,
String.valueOf(2 * targetIsland.getRange()));
return false;
}
if (range == island.getProtectionRange()) {
if (range == targetIsland.getProtectionRange()) {
user.sendMessage("commands.admin.range.invalid-value.same-as-before", TextVariables.NUMBER, args.get(1));
return false;
}
// Get old range for event
int oldRange = island.getProtectionRange();
int oldRange = targetIsland.getProtectionRange();
// Well, now it can be applied without taking any risks!
island.setProtectionRange(range);
targetIsland.setProtectionRange(range);
// Call Protection Range Change event. Does not support canceling.
IslandEvent.builder()
.island(island)
.location(island.getCenter())
.island(targetIsland).location(targetIsland.getCenter())
.reason(IslandEvent.Reason.RANGE_CHANGE)
.involvedPlayer(targetUUID)
.admin(true)
@ -90,14 +65,4 @@ public class AdminRangeSetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(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();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -22,7 +22,7 @@ public class AdminResetsAddCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.resets.add");
setDescription("commands.admin.resets.add.description");
setParametersHelp("commands.admin.resets.add.parameters");
}

View File

@ -22,7 +22,7 @@ public class AdminResetsRemoveCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.resets.remove");
setDescription("commands.admin.resets.remove.description");
setParametersHelp("commands.admin.resets.remove.parameters");
}

View File

@ -22,7 +22,7 @@ public class AdminResetsResetCommand extends ConfirmableCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.resets.remove");
setDescription("commands.admin.resets.reset.description");
setParametersHelp("commands.admin.resets.reset.parameters");
}

View File

@ -16,7 +16,7 @@ public class AdminResetsSetCommand extends CompositeCommand {
@Override
public void setup() {
inheritPermission();
setPermission("admin.resets.set");
setDescription("commands.admin.resets.set.description");
setParametersHelp("commands.admin.resets.set.parameters");
}

View File

@ -21,7 +21,7 @@ public class AdminTeamAddCommand extends CompositeCommand {
@Override
public void setup() {
setPermission("mod.team");
setPermission("mod.team.add");
setParametersHelp("commands.admin.team.add.parameters");
setDescription("commands.admin.team.add.description");
}
@ -44,19 +44,17 @@ public class AdminTeamAddCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(1));
return false;
}
if (!getIslands().hasIsland(getWorld(), ownerUUID)) {
Island island = getIslands().getPrimaryIsland(getWorld(), ownerUUID);
if (island == null || !getIslands().hasIsland(getWorld(), ownerUUID)) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (getIslands().inTeam(getWorld(), ownerUUID) && !getIslands().getOwner(getWorld(), ownerUUID).equals(ownerUUID)) {
if (getIslands().inTeam(getWorld(), ownerUUID) && !ownerUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.team.add.name-not-owner", TextVariables.NAME, args.get(0));
Island island = getIslands().getIsland(getWorld(), ownerUUID);
if (island != null) {
new IslandInfo(island).showMembers(user);
}
new IslandInfo(island).showMembers(user);
return false;
}
if (getIslands().inTeam(getWorld(), targetUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands() && island.inTeam(targetUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}
@ -67,25 +65,19 @@ public class AdminTeamAddCommand extends CompositeCommand {
// Success
User target = User.getInstance(targetUUID);
User owner = User.getInstance(ownerUUID);
owner.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, getPlugin().getPlayers().getName(targetUUID));
owner.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME,
getPlugin().getPlayers().getName(targetUUID));
target.sendMessage("commands.island.team.invite.accept.you-joined-island", TextVariables.LABEL, getTopLabel());
Island teamIsland = getIslands().getIsland(getWorld(), ownerUUID);
if (teamIsland != null) {
getIslands().setJoinTeam(teamIsland, targetUUID);
user.sendMessage("commands.admin.team.add.success", TextVariables.NAME, target.getName(), "[owner]", owner.getName());
TeamEvent.builder()
.island(teamIsland)
.reason(TeamEvent.Reason.JOINED)
.involvedPlayer(targetUUID)
.admin(true)
.build();
IslandEvent.builder()
.island(teamIsland)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(teamIsland.getRank(target), RanksManager.MEMBER_RANK)
.build();
user.sendMessage("commands.admin.team.add.success", TextVariables.NAME, target.getName(), "[owner]",
owner.getName());
TeamEvent.builder().island(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(targetUUID)
.admin(true).build();
IslandEvent.builder().island(teamIsland).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(teamIsland.getRank(target), RanksManager.MEMBER_RANK).build();
return true;
} else {
user.sendMessage("general.errors.player-has-no-island");

View File

@ -0,0 +1,47 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.api.commands.admin.team;
import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
/**
* Parent command for all Admin Team commands.
*/
public class AdminTeamCommand extends CompositeCommand
{
public AdminTeamCommand(CompositeCommand parent)
{
super(parent, "team");
}
@Override
public void setup()
{
this.setPermission("mod.team");
this.setDescription("commands.admin.team.description");
new AdminTeamAddCommand(this);
new AdminTeamDisbandCommand(this);
new AdminTeamKickCommand(this);
new AdminTeamSetownerCommand(this);
}
@Override
public boolean execute(User user, String label, List<String> args)
{
this.showHelp(this, user);
return true;
}
}

View File

@ -1,7 +1,14 @@
package world.bentobox.bentobox.api.commands.admin.team;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
@ -14,66 +21,110 @@ import world.bentobox.bentobox.util.Util;
public class AdminTeamDisbandCommand extends CompositeCommand {
private Island island;
private @Nullable UUID targetUUID;
/**
* Disbands a team
* @param parent parent command
*/
public AdminTeamDisbandCommand(CompositeCommand parent) {
super(parent, "disband");
}
@Override
public void setup() {
setPermission("mod.team");
setPermission("mod.team.disband");
setParametersHelp("commands.admin.team.disband.parameters");
setDescription("commands.admin.team.disband.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
if (args.isEmpty() || args.size() > 2) {
showHelp(this, user);
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!getIslands().hasIsland(getWorld(), targetUUID)) {
user.sendMessage("general.errors.no-island");
// Find the island the player is an owner of
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.isEmpty()) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
if (!getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.not-in-team");
return false;
}
if (!getIslands().getOwner(getWorld(), targetUUID).equals(targetUUID)) {
user.sendMessage("commands.admin.team.disband.use-disband-owner", "[owner]", getPlayers().getName(getIslands().getOwner(getWorld(), targetUUID)));
if (islands.size() > 1) {
if (args.size() != 2 || !islands.containsKey(args.get(1))) {
user.sendMessage("commands.admin.team.disband.more-than-one-island", TextVariables.NAME,
getPlayers().getName(island.getOwner()));
islands.keySet().forEach(coords -> user.sendMessage("commands.admin.team.disband.more-than-one-island",
TextVariables.XYZ, coords));
return false;
}
// Get the named island
island = islands.get(args.get(1));
} else {
// Get the only island
island = islands.values().iterator().next();
}
// Check that the target owns the island
if (island.getOwner() == null || !island.getOwner().equals(targetUUID)) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(0));
return false;
}
return true;
}
private Map<String, Island> getIslandsXYZ(UUID target) {
return getIslands().getOwnedIslands(getWorld(), target).stream().filter(is -> is.getMemberSet().size() > 1) // Filter for teams
.collect(Collectors.toMap(is -> Util.xyz(is.getCenter().toVector()), is -> is));
}
@Override
public boolean execute(User user, String label, List<String> args) {
Objects.requireNonNull(island);
Objects.requireNonNull(targetUUID);
// Disband team
Island island = getIslands().getIsland(getWorld(), targetUUID);
getIslands().getMembers(getWorld(), targetUUID).forEach(m -> {
island.getMemberSet().forEach(m -> {
User mUser = User.getInstance(m);
mUser.sendMessage("commands.admin.team.disband.disbanded");
// The owner gets to keep the island
if (!m.equals(targetUUID)) {
getIslands().setLeaveTeam(getWorld(), m);
TeamEvent.builder()
.island(island)
.reason(TeamEvent.Reason.KICK)
.involvedPlayer(m)
.admin(true)
.build();
IslandEvent.builder()
.island(island)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(mUser), RanksManager.VISITOR_RANK)
.build();
getIslands().removePlayer(island, m);
TeamEvent.builder().island(island).reason(TeamEvent.Reason.KICK).involvedPlayer(m).admin(true).build();
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(mUser), RanksManager.VISITOR_RANK).build();
}
});
user.sendMessage("commands.admin.team.disband.success", TextVariables.NAME, args.get(0));
return true;
}
@Override
public Optional<List<String>> tabComplete(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() == 3) {
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
} else if (args.size() > 3) {
// Find out which user
UUID uuid = getPlayers().getUUID(args.get(1));
if (uuid != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(getIslandsXYZ(uuid).keySet()), lastArg));
}
}
return Optional.empty();
}
}

View File

@ -1,35 +0,0 @@
package world.bentobox.bentobox.api.commands.admin.team;
import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
public class AdminTeamFixCommand extends CompositeCommand {
public AdminTeamFixCommand(CompositeCommand parent) {
super(parent, "fix");
}
@Override
public void setup() {
setPermission("mod.team");
setDescription("commands.admin.team.fix.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (!args.isEmpty()) {
showHelp(this, user);
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
getIslands().checkTeams(user, getWorld());
return true;
}
}

View File

@ -30,7 +30,7 @@ public class AdminTeamKickCommand extends CompositeCommand {
@Override
public void setup() {
setPermission("mod.team");
setPermission("mod.team.kick");
setParametersHelp("commands.admin.team.kick.parameters");
setDescription("commands.admin.team.kick.description");
}
@ -71,7 +71,7 @@ public class AdminTeamKickCommand extends CompositeCommand {
User target = User.getInstance(targetUUID);
target.sendMessage("commands.admin.team.kick.admin-kicked");
getIslands().removePlayer(getWorld(), targetUUID);
getIslands().removePlayer(island, targetUUID);
user.sendMessage("commands.admin.team.kick.success", TextVariables.NAME, target.getName(), "[owner]", getPlayers().getName(island.getOwner()));
// Fire event so add-ons know

View File

@ -2,9 +2,16 @@ package world.bentobox.bentobox.api.commands.admin.team;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -15,9 +22,14 @@ import world.bentobox.bentobox.util.Util;
/**
* Sets the owner of an island.
*
* @author tastybento
*/
public class AdminTeamSetownerCommand extends CompositeCommand {
public class AdminTeamSetownerCommand extends ConfirmableCommand {
private @Nullable UUID targetUUID;
private Island island;
private @Nullable UUID previousOwnerUUID;
public AdminTeamSetownerCommand(CompositeCommand parent) {
super(parent, "setowner");
@ -25,73 +37,95 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
@Override
public void setup() {
setPermission("mod.team");
setPermission("mod.team.setowner");
setParametersHelp("commands.admin.team.setowner.parameters");
setDescription("commands.admin.team.setowner.description");
this.setOnlyPlayer(true);
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
showHelp(this, user);
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.not-in-team");
// Check that user is on an island
Optional<Island> opIsland = getIslands().getIslandAt(user.getLocation());
if (opIsland.isEmpty()) {
user.sendMessage("commands.admin.team.setowner.must-be-on-island");
return false;
}
UUID previousOwnerUUID = getIslands().getOwner(getWorld(), targetUUID);
island = opIsland.get();
previousOwnerUUID = island.getOwner();
if (targetUUID.equals(previousOwnerUUID)) {
user.sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, args.get(0));
return false;
}
return true;
}
// Get the User corresponding to the current owner
public boolean execute(User user, String label, List<String> args) {
Objects.requireNonNull(island);
Objects.requireNonNull(targetUUID);
this.askConfirmation(user, user.getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME,
args.get(0), TextVariables.XYZ, Util.xyz(island.getCenter().toVector())), () -> changeOwner(user));
return true;
}
protected void changeOwner(User user) {
User target = User.getInstance(targetUUID);
// Fire event so add-ons know
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID));
// Call the setowner event
TeamEvent.builder()
.island(island)
.reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID)
.admin(true)
.build();
TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER).involvedPlayer(targetUUID).admin(true)
.build();
// Call the rank change event for the new island owner
// We need to call it BEFORE the actual change, in order to retain the player's previous rank.
IslandEvent.builder()
.island(island)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(target), RanksManager.OWNER_RANK)
.build();
// We need to call it BEFORE the actual change, in order to retain the player's
// previous rank.
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(island.getRank(target), RanksManager.OWNER_RANK)
.build();
// Make new owner
getIslands().setOwner(getWorld(), user, targetUUID);
user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, args.get(0));
getIslands().setOwner(user, targetUUID, island, RanksManager.MEMBER_RANK);
user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, target.getName());
// Report if this made player have more islands than expected
// Get how many islands this player has
int num = this.getIslands().getNumberOfConcurrentIslands(targetUUID, getWorld());
int max = target.getPermissionValue(
this.getIWM().getAddon(getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.number",
this.getIWM().getWorldSettings(getWorld()).getConcurrentIslands());
if (num > max) {
// You cannot make an island
user.sendMessage("commands.admin.team.setowner.extra-islands", TextVariables.NUMBER, String.valueOf(num),
"[max]", String.valueOf(max));
}
// Call the rank change event for the old island owner
if (previousOwnerUUID != null) {
// We need to call it AFTER the actual change.
IslandEvent.builder()
.island(island)
.involvedPlayer(previousOwnerUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.OWNER_RANK, island.getRank(previousOwnerUUID))
.build();
IslandEvent.builder().island(island).involvedPlayer(previousOwnerUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.OWNER_RANK, island.getRank(previousOwnerUUID)).build();
}
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -23,6 +23,7 @@ public abstract class DefaultPlayerCommand extends CompositeCommand {
*/
protected DefaultPlayerCommand(GameModeAddon addon) {
// Register command with alias from config.
// The first command listed is the "label" and the others are aliases.
super(addon,
addon.getWorldSettings().getPlayerCommandAliases().split(" ")[0],
addon.getWorldSettings().getPlayerCommandAliases().split(" "));

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -18,6 +17,7 @@ import world.bentobox.bentobox.api.events.island.IslandEvent;
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.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
public class IslandBanCommand extends CompositeCommand {
@ -46,7 +46,8 @@ public class IslandBanCommand extends CompositeCommand {
}
UUID playerUUID = user.getUniqueId();
// Player issuing the command must have an island or be in a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId()) && !getIslands().hasIsland(getWorld(), user.getUniqueId())) {
if (!getIslands().inTeam(getWorld(), user.getUniqueId())
&& !getIslands().hasIsland(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-island");
return false;
}
@ -54,7 +55,8 @@ public class IslandBanCommand extends CompositeCommand {
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), user));
int rank = island.getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target player
@ -68,7 +70,7 @@ public class IslandBanCommand extends CompositeCommand {
user.sendMessage("commands.island.ban.cannot-ban-yourself");
return false;
}
if (getIslands().getMembers(getWorld(), user.getUniqueId()).contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.ban.cannot-ban-member");
return false;
}
@ -98,25 +100,26 @@ public class IslandBanCommand extends CompositeCommand {
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), issuer.getUniqueId()));
// Check if player can ban any more players
int banLimit = issuer.getPermissionValue(getPermissionPrefix() + "ban.maxlimit", getIWM().getBanLimit(getWorld()));
int banLimit = issuer.getPermissionValue(getPermissionPrefix() + "ban.maxlimit",
getIWM().getBanLimit(getWorld()));
if (banLimit <= -1 || island.getBanned().size() < banLimit) {
// Run the event
IslandBaseEvent banEvent = IslandEvent.builder()
.island(island)
.involvedPlayer(target.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.BAN)
.build();
if (banEvent.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(banEvent.isCancelled()) ) {
IslandBaseEvent banEvent = IslandEvent.builder().island(island).involvedPlayer(target.getUniqueId())
.admin(false).reason(IslandEvent.Reason.BAN).build();
if (banEvent.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(banEvent.isCancelled())) {
// Banning was blocked due to an event cancellation. Fail silently.
return false;
}
// Event is not cancelled
if (island.ban(issuer.getUniqueId(), target.getUniqueId())) {
issuer.sendMessage("commands.island.ban.player-banned", TextVariables.NAME, target.getName());
target.sendMessage("commands.island.ban.owner-banned-you", TextVariables.NAME, issuer.getName());
// If the player is online, has an island and on the banned island, move them home immediately
if (target.isOnline() && getIslands().hasIsland(getWorld(), target.getUniqueId()) && island.onIsland(target.getLocation())) {
issuer.sendMessage("commands.island.ban.player-banned", TextVariables.NAME, target.getName(),
TextVariables.DISPLAY_NAME, target.getDisplayName());
target.sendMessage("commands.island.ban.owner-banned-you", TextVariables.NAME, issuer.getName(),
TextVariables.DISPLAY_NAME, issuer.getDisplayName());
// If the player is online, has an island and on the banned island, move them
// home immediately
if (target.isOnline() && getIslands().hasIsland(getWorld(), target.getUniqueId())
&& island.onIsland(target.getLocation())) {
getIslands().homeTeleportAsync(getWorld(), target.getPlayer());
island.getWorld().playSound(target.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1F, 1F);
}
@ -131,7 +134,7 @@ public class IslandBanCommand extends CompositeCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (lastArg.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
@ -140,9 +143,8 @@ public class IslandBanCommand extends CompositeCommand {
if (island != null) {
List<String> options = Bukkit.getOnlinePlayers().stream()
.filter(p -> !p.getUniqueId().equals(user.getUniqueId()))
.filter(p -> !island.isBanned(p.getUniqueId()))
.filter(p -> user.getPlayer().canSee(p))
.map(Player::getName).collect(Collectors.toList());
.filter(p -> !island.isBanned(p.getUniqueId())).filter(p -> user.getPlayer().canSee(p))
.map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
} else {
return Optional.empty();

View File

@ -3,12 +3,12 @@ package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
public class IslandBanlistCommand extends CompositeCommand {
@ -41,7 +41,8 @@ public class IslandBanlistCommand extends CompositeCommand {
island = getIslands().getIsland(getWorld(), user.getUniqueId());
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
return true;
@ -57,7 +58,7 @@ public class IslandBanlistCommand extends CompositeCommand {
// Title
user.sendMessage("commands.island.banlist.the-following");
// Create a nicely formatted list
List<String> names = island.getBanned().stream().map(u -> getPlayers().getName(u)).sorted().collect(Collectors.toList());
List<String> names = island.getBanned().stream().map(u -> getPlayers().getName(u)).sorted().toList();
List<String> lines = new ArrayList<>();
StringBuilder line = new StringBuilder();
// Put the names into lines of no more than 40 characters long, separated by commas

View File

@ -5,13 +5,15 @@ import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.panels.customizable.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* /island create - Create an island.
@ -22,6 +24,7 @@ public class IslandCreateCommand extends CompositeCommand {
/**
* Command to create an island
*
* @param islandCommand - parent command
*/
public IslandCreateCommand(CompositeCommand islandCommand) {
@ -40,14 +43,29 @@ public class IslandCreateCommand extends CompositeCommand {
public boolean canExecute(User user, String label, List<String> args) {
// Check if the island is reserved
@Nullable
Island island = getIslands().getIsland(getWorld(), user);
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island != null) {
// Reserved islands can be made
if (island.isReserved()) {
return true;
}
}
// Check if this player is on a team in this world
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), user.getUniqueId()) && island != null
&& !user.getUniqueId().equals(island.getOwner())) {
// Team members who are not owners cannot make additional islands
user.sendMessage("commands.island.create.you-cannot-make-team");
return false;
}
// Get how many islands this player has
int num = this.getIslands().getNumberOfConcurrentIslands(user.getUniqueId(), getWorld());
int max = user.getPermissionValue(
this.getIWM().getAddon(getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.number",
this.getIWM().getWorldSettings(getWorld()).getConcurrentIslands());
if (num >= max) {
// You cannot make an island
user.sendMessage("general.errors.already-have-island");
user.sendMessage("commands.island.create.you-cannot-make");
return false;
}
if (getIWM().getMaxIslands(getWorld()) > 0
@ -63,44 +81,67 @@ public class IslandCreateCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
// Check perm
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
// Check maximum uses
if (checkMaxUses(user, name)) {
return false;
}
// Make island
return makeIsland(user, name);
} else {
if (getPlugin().getSettings().getIslandNumber() > 1
&& checkMaxUses(user, BlueprintsManager.DEFAULT_BUNDLE_NAME)) {
return false;
}
// Show panel only if there are multiple bundles available
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).size() > 1) {
// Show panel
IslandCreationPanel.openPanel(this, user, label);
IslandCreationPanel.openPanel(this, user, label, false);
return true;
}
return makeIsland(user, BlueprintsManager.DEFAULT_BUNDLE_NAME);
}
}
private boolean checkMaxUses(User user, String name) {
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).containsKey(name)) {
int maxTimes = getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).get(name).getTimes();
// Check how many times this player has used this bundle
if (maxTimes > 0 && getBundleUses(user, name) >= maxTimes) {
user.sendMessage("commands.island.create.max-uses");
return true;
}
}
return false;
}
private long getBundleUses(User user, String name) {
return getIslands().getIslands(getWorld(), user).stream()
.filter(is -> is.getMetaData("bundle").map(mdv -> name.equalsIgnoreCase(mdv.asString())).orElse(false))
.count();
}
private boolean makeIsland(User user, String name) {
user.sendMessage("commands.island.create.creating-island");
try {
NewIsland.builder()
.player(user)
.addon(getAddon())
.reason(Reason.CREATE)
.name(name)
.build();
NewIsland.builder().player(user).addon(getAddon()).reason(Reason.CREATE).name(name).build();
} catch (IOException e) {
getPlugin().logError("Could not create island for player. " + e.getMessage());
user.sendMessage(e.getMessage());
return false;
}
if (getSettings().isResetCooldownOnCreate()) {
getParent().getSubCommand("reset").ifPresent(resetCommand -> resetCommand.setCooldown(user.getUniqueId(), getSettings().getResetCooldown()));
getParent().getSubCommand("reset").ifPresent(
resetCommand -> resetCommand.setCooldown(user.getUniqueId(), getSettings().getResetCooldown()));
}
return true;
}

View File

@ -1,12 +1,12 @@
package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -22,8 +22,10 @@ import world.bentobox.bentobox.util.Util;
*/
public class IslandDeletehomeCommand extends ConfirmableCommand {
private @Nullable Island island;
/**
* Deletes a home
* @param islandCommand parent command
*/
public IslandDeletehomeCommand(CompositeCommand islandCommand) {
super(islandCommand, "deletehome");
}
@ -44,34 +46,36 @@ public class IslandDeletehomeCommand extends ConfirmableCommand {
this.showHelp(this, user);
return false;
}
island = getIslands().getIsland(getWorld(), user);
Island island = getIslands().getIsland(getWorld(), user);
// Check island
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
}
// check command permission
// check command ranks
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank",
TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
TextVariables.RANK, user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Check if the name is known
if (!getIslands().isHomeLocation(island, String.join(" ", args))) {
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
island.getHomes().keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("home-list-syntax", TextVariables.NAME, s));
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
this.askConfirmation(user, () -> delete(island, user, String.join(" ", args)));
// Check if the name is known
Map<String, Island> map = getNameIslandMap(user);
String name = String.join(" ", args);
if (!map.containsKey(name)) {
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
map.keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s));
return false;
}
this.askConfirmation(user, () -> delete(map.get(name), user, name));
return true;
}
@ -84,11 +88,19 @@ public class IslandDeletehomeCommand extends ConfirmableCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(island.getHomes().keySet()), lastArg));
} else {
return Optional.empty();
}
return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(user).keySet()), lastArg));
}
private Map<String, Island> getNameIslandMap(User user) {
Map<String, Island> islandMap = new HashMap<>();
for (Island isle : getIslands().getIslands(getWorld(), user.getUniqueId())) {
// Add homes.
isle.getHomes().keySet().forEach(name -> islandMap.put(name, isle));
}
return islandMap;
}
}

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -17,6 +16,7 @@ import world.bentobox.bentobox.api.events.island.IslandEvent;
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.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
@ -60,7 +60,8 @@ public class IslandExpelCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target player
@ -75,7 +76,7 @@ public class IslandExpelCommand extends CompositeCommand {
return false;
}
// Or team member
if (getIslands().getMembers(getWorld(), user.getUniqueId()).contains(targetUUID)) {
if (island.inTeam(targetUUID)) {
user.sendMessage("commands.island.expel.cannot-expel-member");
return false;
}
@ -91,53 +92,48 @@ public class IslandExpelCommand extends CompositeCommand {
return false;
}
// Cannot ban ops
if (target.isOp() ||
target.hasPermission(this.getPermissionPrefix() + "admin.noexpel") ||
target.hasPermission(this.getPermissionPrefix() + "mod.bypassexpel")) {
if (target.isOp() || target.hasPermission(this.getPermissionPrefix() + "admin.noexpel")
|| target.hasPermission(this.getPermissionPrefix() + "mod.bypassexpel")) {
user.sendMessage(CANNOT_EXPEL);
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Finished error checking - expel player
Island island = getIslands().getIsland(getWorld(), user);
// Fire event
IslandBaseEvent expelEvent = IslandEvent.builder()
.island(island)
.involvedPlayer(target.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.EXPEL)
.build();
IslandBaseEvent expelEvent = IslandEvent.builder().island(island).involvedPlayer(target.getUniqueId())
.admin(false).reason(IslandEvent.Reason.EXPEL).build();
if (expelEvent.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(expelEvent.isCancelled())) {
user.sendMessage(CANNOT_EXPEL);
return false;
}
target.sendMessage("commands.island.expel.player-expelled-you", TextVariables.NAME, user.getName());
target.sendMessage("commands.island.expel.player-expelled-you", TextVariables.NAME, user.getName(),
TextVariables.DISPLAY_NAME, user.getDisplayName());
island.getWorld().playSound(target.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1F, 1F);
if (getIslands().hasIsland(getWorld(), target) || getIslands().inTeam(getWorld(), target.getUniqueId())) {
// Success
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName());
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME,
target.getDisplayName());
// Teleport home
getIslands().homeTeleportAsync(getWorld(), target.getPlayer());
return true;
} else if (getIslands().getSpawn(getWorld()).isPresent()){
} else if (getIslands().getSpawn(getWorld()).isPresent()) {
// Success
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName());
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME,
target.getDisplayName());
getIslands().spawnTeleport(getWorld(), target.getPlayer());
return true;
} else if (getIWM().getAddon(getWorld())
.map(gm -> gm.getPlayerCommand()
.map(pc -> pc.getSubCommand("create").isPresent())
.orElse(false))
.orElse(false)
&& target.performCommand(this.getTopLabel() + " create")) {
.map(gm -> gm.getPlayerCommand().map(pc -> pc.getSubCommand("create").isPresent()).orElse(false))
.orElse(false) && target.performCommand(this.getTopLabel() + " create")) {
getAddon().logWarning("Expel: " + target.getName() + " had no island, so one was created");
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName());
user.sendMessage(SUCCESS, TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME,
target.getDisplayName());
return true;
}
@ -150,15 +146,15 @@ public class IslandExpelCommand extends CompositeCommand {
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
Island island = getIslands().getIsland(getWorld(), user);
if (island != null) {
List<String> options = island.getPlayersOnIsland().stream()
.filter(p -> !p.equals(user.getPlayer())) // Not self
List<String> options = island.getPlayersOnIsland().stream().filter(p -> !p.equals(user.getPlayer())) // Not
// self
.filter(p -> user.getPlayer().canSee(p)) // Not invisible
.filter(p -> !p.isOp()) // Not op
.filter(p -> !p.hasPermission(this.getPermissionPrefix() + "admin.noexpel"))
.filter(p -> !p.hasPermission(this.getPermissionPrefix() + "mod.bypassexpel"))
.map(Player::getName).collect(Collectors.toList());
.filter(p -> !p.hasPermission(this.getPermissionPrefix() + "mod.bypassexpel")).map(Player::getName)
.toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {
return Optional.empty();

View File

@ -2,7 +2,9 @@ package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.CompositeCommand;
@ -38,49 +40,92 @@ public class IslandGoCommand extends DelayedTeleportCommand {
user.sendMessage("commands.island.go.teleport");
return false;
}
// Check if the island is reserved
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island == null) {
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
if (islands.isEmpty()) {
user.sendMessage("general.errors.no-island");
return false;
}
if (island.isReserved()) {
// Send player to create an island
getParent().getSubCommand("create").ifPresent(createCmd -> createCmd.call(user, createCmd.getLabel(), Collections.emptyList()));
// Check if the island is reserved
if (checkReserved(user, islands)) {
return false;
}
// Prevent command if player is falling and its not allowed
if ((getIWM().inWorld(user.getWorld()) && Flags.PREVENT_TELEPORT_WHEN_FALLING.isSetForWorld(user.getWorld()))
&& user.getPlayer().getFallDistance() > 0) {
// We're sending the "hint" to the player to tell them they cannot teleport while falling.
user.sendMessage(Flags.PREVENT_TELEPORT_WHEN_FALLING.getHintReference());
return false;
}
if (!args.isEmpty()) {
if (!getIslands().isHomeLocation(island, String.join(" ", args))) {
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
island.getHomes().keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s));
return false;
}
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer(), String.join(" ", args)));
// Check if the home is known
if (!args.isEmpty()) {
Map<String, IslandInfo> names = getNameIslandMap(user);
final String name = String.join(" ", args);
if (!names.containsKey(name)) {
// Failed home name check
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
names.keySet().forEach(n -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, n));
return false;
} else {
IslandInfo info = names.get(name);
getIslands().setPrimaryIsland(user.getUniqueId(), info.island);
if (!info.islandName) {
this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer(), name)
.thenAccept((r) -> getIslands().setPrimaryIsland(user.getUniqueId(), info.island)));
return true;
}
}
}
this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer()));
return true;
}
private boolean checkReserved(User user, List<Island> islands) {
for (Island island : islands) {
if (island.isReserved()) {
// Send player to create an island
getParent().getSubCommand("create").ifPresent(createCmd -> createCmd.call(user, createCmd.getLabel(), Collections.emptyList()));
return true;
}
}
return false;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(island.getHomes().keySet()), lastArg));
} else {
return Optional.empty();
return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(user).keySet()), lastArg));
}
private record IslandInfo(Island island, boolean islandName) {}
private Map<String, IslandInfo> getNameIslandMap(User user) {
Map<String, IslandInfo> islandMap = new HashMap<>();
int index = 0;
for (Island island : getIslands().getIslands(getWorld(), user.getUniqueId())) {
index++;
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
islandMap.put(island.getName(), new IslandInfo(island, true));
} else {
// Name has not been set
String text = user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index;
islandMap.put(text, new IslandInfo(island, true));
}
// Add homes. Homes do not need an island specified
island.getHomes().keySet().forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
}
return islandMap;
}
}

View File

@ -1,18 +1,19 @@
package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import java.util.Optional;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
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;
public class IslandHomesCommand extends ConfirmableCommand {
private @Nullable Island island;
private List<Island> islands;
public IslandHomesCommand(CompositeCommand islandCommand) {
super(islandCommand, "homes");
@ -27,9 +28,9 @@ public class IslandHomesCommand extends ConfirmableCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
island = getIslands().getIsland(getWorld(), user);
islands = getIslands().getIslands(getWorld(), user);
// Check island
if (island == null || island.getOwner() == null) {
if (islands.isEmpty()) {
user.sendMessage("general.errors.no-island");
return false;
}
@ -39,9 +40,21 @@ public class IslandHomesCommand extends ConfirmableCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendMessage("commands.island.sethome.homes-are");
islands.forEach(island ->
island.getHomes().keySet().stream().filter(s -> !s.isEmpty())
.forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s));
.forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s)));
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
List<String> result = new ArrayList<>();
for (Island island : getIslands().getIslands(getWorld(), user.getUniqueId())) {
result.addAll(island.getHomes().keySet());
}
return Optional.of(Util.tabLimit(result, lastArg));
}
}

View File

@ -7,7 +7,7 @@ import java.util.Optional;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.panels.LanguagePanel;
import world.bentobox.bentobox.panels.customizable.LanguagePanel;
import world.bentobox.bentobox.util.Util;
/**
@ -46,7 +46,7 @@ public class IslandLanguageCommand extends CompositeCommand {
return false;
}
} else {
LanguagePanel.openPanel(user);
LanguagePanel.openPanel(this, user);
}
return true;
}

View File

@ -65,7 +65,8 @@ public class IslandRenamehomeCommand extends ConfirmableCommand {
// check command permission
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
@ -87,9 +88,9 @@ public class IslandRenamehomeCommand extends ConfirmableCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(island.getHomes().keySet()), lastArg));
Island is = getIslands().getIsland(getWorld(), user.getUniqueId());
if (is != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(is.getHomes().keySet()), lastArg));
} else {
return Optional.empty();
}

View File

@ -16,7 +16,8 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.managers.island.NewIsland.Builder;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.panels.customizable.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -31,8 +32,9 @@ public class IslandResetCommand extends ConfirmableCommand {
/**
* Creates the island reset command
*
* @param islandCommand - parent command
* @param noPaste - true if resetting should not paste a new island
* @param noPaste - true if resetting should not paste a new island
*/
public IslandResetCommand(CompositeCommand islandCommand, boolean noPaste) {
super(islandCommand, "reset", "restart");
@ -78,20 +80,21 @@ public class IslandResetCommand extends ConfirmableCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null || name.isEmpty()) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
return resetIsland(user, name);
} else {
// Show panel after confirmation
if (getPlugin().getSettings().isResetConfirmation()) {
this.askConfirmation(user, user.getTranslation("commands.island.reset.confirmation"), () -> selectBundle(user, label));
this.askConfirmation(user, user.getTranslation("commands.island.reset.confirmation"),
() -> selectBundle(user, label));
} else {
selectBundle(user, label);
}
@ -101,13 +104,14 @@ public class IslandResetCommand extends ConfirmableCommand {
/**
* Either selects the bundle to use or asks the user to choose.
*
* @since 1.5.1
*/
private void selectBundle(@NonNull User user, @NonNull String label) {
// Show panel only if there are multiple bundles available
if (getPlugin().getBlueprintsManager().getBlueprintBundles(getAddon()).size() > 1) {
// Show panel - once the player selected a bundle, this will re-run this command
IslandCreationPanel.openPanel(this, user, label);
IslandCreationPanel.openPanel(this, user, label, true);
} else {
resetIsland(user, BlueprintsManager.DEFAULT_BUNDLE_NAME);
}
@ -115,6 +119,7 @@ public class IslandResetCommand extends ConfirmableCommand {
/**
* Reset island
*
* @param user user
* @param name name of Blueprint Bundle
* @return true if successful
@ -122,19 +127,15 @@ public class IslandResetCommand extends ConfirmableCommand {
private boolean resetIsland(User user, String name) {
// Get the player's old island
Island oldIsland = getIslands().getIsland(getWorld(), user);
if (oldIsland != null) {
deleteOldIsland(user, oldIsland);
}
deleteOldIsland(user, oldIsland);
user.sendMessage("commands.island.create.creating-island");
// Create new island and then delete the old one
try {
Builder builder = NewIsland.builder()
.player(user)
.reason(Reason.RESET)
.addon(getAddon())
.oldIsland(oldIsland)
.name(name);
if (noPaste) builder.noPaste();
Builder builder = NewIsland.builder().player(user).reason(Reason.RESET).addon(getAddon())
.oldIsland(oldIsland).name(name);
if (noPaste)
builder.noPaste();
builder.build();
} catch (IOException e) {
getPlugin().logError("Could not create island for player. " + e.getMessage());
@ -147,13 +148,8 @@ public class IslandResetCommand extends ConfirmableCommand {
private void deleteOldIsland(User user, Island oldIsland) {
// Fire island preclear event
IslandEvent.builder()
.involvedPlayer(user.getUniqueId())
.reason(Reason.PRECLEAR)
.island(oldIsland)
.oldIsland(oldIsland)
.location(oldIsland.getCenter())
.build();
IslandEvent.builder().involvedPlayer(user.getUniqueId()).reason(Reason.PRECLEAR).island(oldIsland)
.oldIsland(oldIsland).location(oldIsland.getCenter()).build();
// Reset the island
@ -166,33 +162,33 @@ public class IslandResetCommand extends ConfirmableCommand {
/**
* Kicks the members (incl. owner) of the island.
*
* @since 1.7.0
*/
private void kickMembers(Island island) {
/*
* We cannot assume the island owner can run /[cmd] team kick (it might be disabled, or there could be permission restrictions...)
* Therefore, we need to do it manually.
* Plus, a more specific team event (TeamDeleteEvent) is called by this method.
* We cannot assume the island owner can run /[cmd] team kick (it might be
* disabled, or there could be permission restrictions...) Therefore, we need to
* do it manually. Plus, a more specific team event (TeamDeleteEvent) is called
* by this method.
*/
island.getMemberSet().forEach(memberUUID -> {
User member = User.getInstance(memberUUID);
// Send a "you're kicked" message if the member is not the island owner (send before removing!)
// Send a "you're kicked" message if the member is not the island owner (send
// before removing!)
if (!memberUUID.equals(island.getOwner())) {
member.sendMessage("commands.island.reset.kicked-from-island", TextVariables.GAMEMODE, getAddon().getDescription().getName());
member.sendMessage("commands.island.reset.kicked-from-island", TextVariables.GAMEMODE,
getAddon().getDescription().getName());
}
// Remove player
getIslands().removePlayer(getWorld(), memberUUID);
getIslands().removePlayer(island, memberUUID);
// Clean player
getPlayers().cleanLeavingPlayer(getWorld(), member, false);
getPlayers().cleanLeavingPlayer(getWorld(), member, false, island);
// Fire event
TeamEvent.builder()
.island(island)
.reason(TeamEvent.Reason.DELETE)
.involvedPlayer(memberUUID)
.build();
TeamEvent.builder().island(island).reason(TeamEvent.Reason.DELETE).involvedPlayer(memberUUID).build();
});
}
}

View File

@ -7,6 +7,7 @@ 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.managers.RanksManager;
/**
@ -41,7 +42,7 @@ public class IslandResetnameCommand extends CompositeCommand {
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank",
TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
TextVariables.RANK, user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}

View File

@ -46,16 +46,19 @@ public class IslandSethomeCommand extends ConfirmableCommand {
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Check number of homes
int maxHomes = getIslands().getMaxHomes(island);
if (getIslands().getNumberOfHomesIfAdded(island, String.join(" ", args)) > maxHomes) {
user.sendMessage("commands.island.sethome.too-many-homes", TextVariables.NUMBER, String.valueOf(maxHomes));
user.sendMessage("commands.island.sethome.homes-are");
island.getHomes().keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s));
getIslands().getIslands(getWorld(), user).forEach(is ->
is.getHomes().keySet().stream().filter(s -> !s.isEmpty()).forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s)));
return false;
}
return true;

View File

@ -6,9 +6,11 @@ import java.util.Objects;
import org.bukkit.ChatColor;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
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.managers.RanksManager;
/**
@ -50,7 +52,8 @@ public class IslandSetnameCommand extends CompositeCommand {
// Check command rank.
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
@ -72,12 +75,6 @@ public class IslandSetnameCommand extends CompositeCommand {
name = ChatColor.translateAlternateColorCodes('&', name);
}
// Check if the name doesn't already exist in the gamemode
if (getSettings().isNameUniqueness() && getIslands().nameExists(getWorld(), name)) {
user.sendMessage("commands.island.setname.name-already-exists");
return false;
}
return true;
}
@ -93,8 +90,18 @@ public class IslandSetnameCommand extends CompositeCommand {
}
// Everything's good!
Objects.requireNonNull(getIslands().getIsland(getWorld(), user)).setName(name);
Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), user));
String previousName = island.getName();
island.setName(name);
user.sendMessage("commands.island.setname.success", TextVariables.NAME, name);
// Fire the IslandNameEvent
new IslandEvent.IslandEventBuilder()
.island(island)
.involvedPlayer(user.getUniqueId())
.reason(IslandEvent.Reason.NAME)
.previousName(previousName)
.admin(false)
.build();
return true;
}
}

View File

@ -47,9 +47,10 @@ public class IslandSettingsCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) {
new TabbedPanelBuilder()
.user(user)
.island(island)
.world(island.getWorld())
.tab(1, new SettingsTab(user, island, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, island, Flag.Type.SETTING))
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(getWorld(), user, Flag.Type.SETTING))
.startingSlot(1)
.size(54)
.hideIfEmpty()

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
@ -14,6 +13,7 @@ import world.bentobox.bentobox.api.events.island.IslandEvent;
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.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
@ -55,7 +55,8 @@ public class IslandUnbanCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target player
@ -95,8 +96,8 @@ public class IslandUnbanCommand extends CompositeCommand {
}
// Event is not cancelled
if (island.unban(user.getUniqueId(), target.getUniqueId())) {
user.sendMessage("commands.island.unban.player-unbanned", TextVariables.NAME, target.getName());
target.sendMessage("commands.island.unban.you-are-unbanned", TextVariables.NAME, user.getName());
user.sendMessage("commands.island.unban.player-unbanned", TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME, target.getDisplayName());
target.sendMessage("commands.island.unban.you-are-unbanned", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
// Set cooldown
if (getSettings().getBanCooldown() > 0 && getParent() != null) {
getParent().getSubCommand("ban").ifPresent(subCommand ->
@ -112,7 +113,7 @@ public class IslandUnbanCommand extends CompositeCommand {
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
List<String> options = island.getBanned().stream().map(getPlayers()::getName).collect(Collectors.toList());
List<String> options = island.getBanned().stream().map(getPlayers()::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {

View File

@ -1,83 +0,0 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.Objects;
import java.util.UUID;
/**
* Represents an invite
* @author tastybento
* @since 1.8.0
*/
public class Invite {
/**
* Type of invitation
*
*/
public enum Type {
COOP,
TEAM,
TRUST
}
private final Type type;
private final UUID inviter;
private final UUID invitee;
/**
* @param type - invitation type, e.g., coop, team, trust
* @param inviter - UUID of inviter
* @param invitee - UUID of invitee
*/
public Invite(Type type, UUID inviter, UUID invitee) {
this.type = type;
this.inviter = inviter;
this.invitee = invitee;
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the inviter
*/
public UUID getInviter() {
return inviter;
}
/**
* @return the invitee
*/
public UUID getInvitee() {
return invitee;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(invitee, inviter, type);
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Invite other)) {
return false;
}
return Objects.equals(invitee, other.invitee) && Objects.equals(inviter, other.inviter) && type == other.type;
}
}

View File

@ -1,16 +1,10 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
@ -18,22 +12,44 @@ import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
public class IslandTeamCommand extends CompositeCommand {
/**
* Invited list. Key is the invited party, value is the invite.
* @since 1.8.0
*/
private final Map<UUID, Invite> inviteMap;
private IslandTeamKickCommand kickCommand;
private IslandTeamLeaveCommand leaveCommand;
private IslandTeamSetownerCommand setOwnerCommand;
private IslandTeamUncoopCommand uncoopCommand;
private IslandTeamUntrustCommand unTrustCommand;
private @Nullable TemplateItem border;
private @Nullable TemplateItem background;
private IslandTeamInviteAcceptCommand acceptCommand;
private IslandTeamInviteRejectCommand rejectCommand;
private IslandTeamInviteCommand inviteCommand;
private IslandTeamCoopCommand coopCommand;
private IslandTeamTrustCommand trustCommand;
private final Database<TeamInvite> handler;
public IslandTeamCommand(CompositeCommand parent) {
super(parent, "team");
inviteMap = new HashMap<>();
handler = new Database<>(parent.getAddon(), TeamInvite.class);
}
@Override
@ -42,139 +58,74 @@ public class IslandTeamCommand extends CompositeCommand {
setOnlyPlayer(true);
setDescription("commands.island.team.description");
// Register commands
new IslandTeamInviteCommand(this);
new IslandTeamLeaveCommand(this);
new IslandTeamSetownerCommand(this);
new IslandTeamKickCommand(this);
new IslandTeamInviteAcceptCommand(this);
new IslandTeamInviteRejectCommand(this);
new IslandTeamCoopCommand(this);
new IslandTeamUncoopCommand(this);
new IslandTeamTrustCommand(this);
new IslandTeamUntrustCommand(this);
inviteCommand = new IslandTeamInviteCommand(this);
leaveCommand = new IslandTeamLeaveCommand(this);
setOwnerCommand = new IslandTeamSetownerCommand(this);
kickCommand = new IslandTeamKickCommand(this);
acceptCommand = new IslandTeamInviteAcceptCommand(this);
rejectCommand = new IslandTeamInviteRejectCommand(this);
if (RanksManager.getInstance().rankExists(RanksManager.COOP_RANK_REF)) {
coopCommand = new IslandTeamCoopCommand(this);
uncoopCommand = new IslandTeamUncoopCommand(this);
}
if (RanksManager.getInstance().rankExists(RanksManager.TRUSTED_RANK_REF)) {
trustCommand = new IslandTeamTrustCommand(this);
unTrustCommand = new IslandTeamUntrustCommand(this);
}
new IslandTeamPromoteCommand(this, "promote");
new IslandTeamPromoteCommand(this, "demote");
// Panels
if (!new File(getPlugin().getDataFolder() + File.separator + "panels", "team_panel.yml").exists()) {
getPlugin().saveResource("panels/team_panel.yml", false);
}
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
// Player issuing the command must have an island
UUID ownerUUID = getOwner(getWorld(), user);
if (ownerUUID == null) {
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
if (isInvited(user.getUniqueId())) {
// Player has an invite, so show the invite
new IslandTeamGUI(getPlugin(), this, user, island).build();
return true;
}
user.sendMessage("general.errors.no-island");
return false;
}
UUID playerUUID = user.getUniqueId();
// Fire event so add-ons can run commands, etc.
if (fireEvent(user)) {
if (fireEvent(user, island)) {
// Cancelled
return false;
}
Island island = getIslands().getIsland(getWorld(), playerUUID);
if (island == null) {
return false;
}
Set<UUID> teamMembers = getMembers(getWorld(), user);
if (ownerUUID.equals(playerUUID)) {
if (playerUUID.equals(island.getOwner())) {
int maxSize = getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK);
if (teamMembers.size() < maxSize) {
user.sendMessage("commands.island.team.invite.you-can-invite", TextVariables.NUMBER, String.valueOf(maxSize - teamMembers.size()));
user.sendMessage("commands.island.team.invite.you-can-invite", TextVariables.NUMBER,
String.valueOf(maxSize - teamMembers.size()));
} else {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
}
}
// Show members of island
showMembers(island, user);
return true;
}
private void showMembers(Island island, User user) {
// Gather online members
List<UUID> onlineMembers = island
.getMemberSet(RanksManager.MEMBER_RANK)
.stream()
.filter(uuid -> Bukkit.getOfflinePlayer(uuid)
.isOnline())
.collect(Collectors.toList());
// List of ranks that we will loop through
Integer[] ranks = new Integer[]{RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK, RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK};
// Show header:
user.sendMessage("commands.island.team.info.header",
"[max]", String.valueOf(getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)),
"[total]", String.valueOf(island.getMemberSet().size()),
"[online]", String.valueOf(onlineMembers.size()));
// We now need to get all online "members" of the island - incl. Trusted and coop
onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())).collect(Collectors.toList());
for (int rank : ranks) {
Set<UUID> players = island.getMemberSet(rank, false);
if (!players.isEmpty()) {
if (rank == RanksManager.OWNER_RANK) {
// Slightly special handling for the owner rank
user.sendMessage("commands.island.team.info.rank-layout.owner",
TextVariables.RANK, user.getTranslation(RanksManager.OWNER_RANK_REF));
} else {
user.sendMessage("commands.island.team.info.rank-layout.generic",
TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)),
TextVariables.NUMBER, String.valueOf(island.getMemberSet(rank, false).size()));
}
for (UUID member : island.getMemberSet(rank, false)) {
OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
if (onlineMembers.contains(member)) {
// the player is online
user.sendMessage("commands.island.team.info.member-layout.online",
TextVariables.NAME, offlineMember.getName());
} else {
// A bit of handling for the last joined date
Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
Instant now = Instant.now();
Duration duration = Duration.between(lastJoined, now);
String lastSeen;
final String reference = "commands.island.team.info.last-seen.layout";
if (duration.toMinutes() < 60L) {
lastSeen = user.getTranslation(reference,
TextVariables.NUMBER, String.valueOf(duration.toMinutes()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.minutes"));
} else if (duration.toHours() < 24L) {
lastSeen = user.getTranslation(reference,
TextVariables.NUMBER, String.valueOf(duration.toHours()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours"));
} else {
lastSeen = user.getTranslation(reference,
TextVariables.NUMBER, String.valueOf(duration.toDays()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days"));
}
if(island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(member)) {
user.sendMessage("commands.island.team.info.member-layout.offline",
TextVariables.NAME, offlineMember.getName(),
"[last_seen]", lastSeen);
}else{
// This will prevent anyone that is trusted or below to not have a last-seen status
user.sendMessage("commands.island.team.info.member-layout.offline-not-last-seen",
TextVariables.NAME, offlineMember.getName());
}
}
}
}
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Show the panel
new IslandTeamGUI(getPlugin(), this, user, getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()))
.build();
return true;
}
private boolean fireEvent(User user) {
IslandBaseEvent e = TeamEvent.builder()
.island(getIslands()
.getIsland(getWorld(), user.getUniqueId()))
.reason(TeamEvent.Reason.INFO)
.involvedPlayer(user.getUniqueId())
.build();
return e.getNewEvent().map(IslandBaseEvent::isCancelled)
.orElse(e.isCancelled());
private boolean fireEvent(User user, Island island) {
IslandBaseEvent e = TeamEvent.builder().island(island).reason(TeamEvent.Reason.INFO)
.involvedPlayer(user.getUniqueId()).build();
return e.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(e.isCancelled());
}
/**
@ -184,8 +135,8 @@ public class IslandTeamCommand extends CompositeCommand {
* @param invitee - uuid of invitee
* @since 1.8.0
*/
public void addInvite(Invite.Type type, @NonNull UUID inviter, @NonNull UUID invitee) {
inviteMap.put(invitee, new Invite(type, inviter, invitee));
public void addInvite(TeamInvite.Type type, @NonNull UUID inviter, @NonNull UUID invitee, @NonNull Island island) {
handler.saveObjectAsync(new TeamInvite(type, inviter, invitee, island.getUniqueId()));
}
/**
@ -195,7 +146,7 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
public boolean isInvited(@NonNull UUID invitee) {
return inviteMap.containsKey(invitee);
return handler.objectExists(invitee.toString());
}
/**
@ -206,7 +157,7 @@ public class IslandTeamCommand extends CompositeCommand {
*/
@Nullable
public UUID getInviter(UUID invitee) {
return isInvited(invitee) ? inviteMap.get(invitee).getInviter() : null;
return isInvited(invitee) ? handler.loadObject(invitee.toString()).getInviter() : null;
}
/**
@ -216,8 +167,8 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
@Nullable
public Invite getInvite(UUID invitee) {
return inviteMap.get(invitee);
public TeamInvite getInvite(UUID invitee) {
return handler.loadObject(invitee.toString());
}
/**
@ -226,6 +177,68 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
public void removeInvite(@NonNull UUID invitee) {
inviteMap.remove(invitee);
handler.deleteID(invitee.toString());
}
/**
* @return the coopCommand
*/
protected IslandTeamCoopCommand getCoopCommand() {
return coopCommand;
}
/**
* @return the trustCommand
*/
protected IslandTeamTrustCommand getTrustCommand() {
return trustCommand;
}
public IslandTeamInviteCommand getInviteCommand() {
return inviteCommand;
}
public IslandTeamInviteAcceptCommand getAcceptCommand() {
return acceptCommand;
}
public IslandTeamInviteRejectCommand getRejectCommand() {
return rejectCommand;
}
/**
* @return the kickCommand
*/
public IslandTeamKickCommand getKickCommand() {
return kickCommand;
}
/**
* @return the leaveCommand
*/
public IslandTeamLeaveCommand getLeaveCommand() {
return leaveCommand;
}
/**
* @return the setOwnerCommand
*/
public IslandTeamSetownerCommand getSetOwnerCommand() {
return setOwnerCommand;
}
/**
* @return the uncoopCommand
*/
public IslandTeamUncoopCommand getUncoopCommand() {
return uncoopCommand;
}
/**
* @return the unTrustCommand
*/
public IslandTeamUntrustCommand getUnTrustCommand() {
return unTrustCommand;
}
}

View File

@ -8,15 +8,16 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
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.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Command to coop another player
*
* @author tastybento
*
*/
@ -47,7 +48,8 @@ public class IslandTeamCoopCommand extends CompositeCommand {
return false;
}
// Player issuing the command must have an island or be in a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId()) && !getIslands().hasIsland(getWorld(), user.getUniqueId())) {
if (!getIslands().inTeam(getWorld(), user.getUniqueId())
&& !getIslands().hasIsland(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-island");
return false;
}
@ -55,7 +57,8 @@ public class IslandTeamCoopCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target player
@ -73,11 +76,13 @@ public class IslandTeamCoopCommand extends CompositeCommand {
user.sendMessage("commands.island.team.coop.cannot-coop-yourself");
return false;
}
if (getIslands().getMembers(getWorld(), user.getUniqueId(), RanksManager.COOP_RANK).contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet(RanksManager.COOP_RANK)
.contains(targetUUID)) {
user.sendMessage("commands.island.team.coop.already-has-rank");
return false;
}
if (itc.isInvited(targetUUID) && itc.getInviter(targetUUID).equals(user.getUniqueId()) && itc.getInvite(targetUUID).getType().equals(Type.COOP)) {
if (itc.isInvited(targetUUID) && user.getUniqueId().equals(itc.getInviter(targetUUID))
&& itc.getInvite(targetUUID) != null && itc.getInvite(targetUUID).getType().equals(Type.COOP)) {
// Prevent spam
user.sendMessage("commands.island.team.invite.errors.you-have-already-invited");
return false;
@ -92,21 +97,27 @@ public class IslandTeamCoopCommand extends CompositeCommand {
if (island != null) {
if (getPlugin().getSettings().isInviteConfirmation()) {
// Put the invited player (key) onto the list with inviter (value)
// If someone else has invited a player, then this invite will overwrite the previous invite!
itc.addInvite(Invite.Type.COOP, user.getUniqueId(), target.getUniqueId());
// If someone else has invited a player, then this invite will overwrite the
// previous invite!
itc.addInvite(Type.COOP, user.getUniqueId(), target.getUniqueId(), island);
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, target.getName());
// Send message to online player
target.sendMessage("commands.island.team.coop.name-has-invited-you", TextVariables.NAME, user.getName());
target.sendMessage("commands.island.team.invite.to-accept-or-reject", TextVariables.LABEL, getTopLabel());
target.sendMessage("commands.island.team.coop.name-has-invited-you", TextVariables.NAME,
user.getName());
target.sendMessage("commands.island.team.invite.to-accept-or-reject", TextVariables.LABEL,
getTopLabel());
} else {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() >= getIslands().getMaxMembers(island, RanksManager.COOP_RANK)) {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() >= getIslands().getMaxMembers(island,
RanksManager.COOP_RANK)) {
user.sendMessage("commands.island.team.coop.is-full");
return false;
}
island.setRank(target, RanksManager.COOP_RANK);
user.sendMessage("commands.island.team.coop.success", TextVariables.NAME, target.getName());
target.sendMessage("commands.island.team.coop.you-are-a-coop-member", TextVariables.NAME, user.getName());
user.sendMessage("commands.island.team.coop.success", TextVariables.NAME, target.getName(),
TextVariables.DISPLAY_NAME, target.getDisplayName());
target.sendMessage("commands.island.team.coop.you-are-a-coop-member", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
}
return true;
} else {
@ -118,7 +129,7 @@ public class IslandTeamCoopCommand extends CompositeCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (lastArg.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();

View File

@ -0,0 +1,568 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Sound;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
public class IslandTeamGUI {
/**
* List of ranks that we will loop through in order
*/
private static final List<Integer> RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK,
RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK);
private static final String NAME = ".name";
private static final String TIPS = "commands.island.team.gui.tips.";
private final User user;
private final Island island;
private int rankView = RanksManager.OWNER_RANK;
private @Nullable TemplateItem border;
private @Nullable TemplateItem background;
private final IslandTeamCommand parent;
private final BentoBox plugin;
/**
* Displays the team management GUI
* @param plugin BentoBox
* @param parent IslandTeamCommand object
* @param user user who is opening the GUI
* @param island island that the GUI is managing
*/
public IslandTeamGUI(BentoBox plugin, IslandTeamCommand parent, User user, Island island) {
this.parent = parent;
this.plugin = plugin;
this.user = user;
this.island = island;
// Panels
if (!new File(plugin.getDataFolder() + File.separator + "panels", "team_panel.yml").exists()) {
plugin.saveResource("panels/team_panel.yml", false);
}
}
/**
* This method builds this GUI.
*/
public void build() {
// Start building panel.
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(user);
panelBuilder.world(user.getWorld());
panelBuilder.template("team_panel", new File(plugin.getDataFolder(), "panels"));
panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName());
panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton);
panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton);
panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton);
panelBuilder.registerTypeBuilder("RANK", this::createRankButton);
panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton);
border = panelBuilder.getPanelTemplate().border();
background = panelBuilder.getPanelTemplate().background();
// Register unknown type builder.
panelBuilder.build();
}
private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
if (island == null || !user.hasPermission(this.parent.getInviteCommand().getPermission())
|| island.getRank(user) < island.getRankCommand(parent.getLabel() + " invite")) {
return this.getBlankBorder();
}
PanelItemBuilder builder = new PanelItemBuilder();
builder.icon(Material.PLAYER_HEAD);
builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name"));
builder.description(user.getTranslation("commands.island.team.gui.buttons.invite.description"));
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (template.actions().stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.LEFT)) {
user.closeInventory();
new IslandTeamInviteGUI(parent, false, island).build(user);
}
return true;
});
return builder.build();
}
private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
// If there is no island, the do not show this icon
if (island == null) {
return this.getBlankBorder();
}
PanelItemBuilder builder = new PanelItemBuilder();
builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name"));
builder.icon(Material.AMETHYST_SHARD);
// Create description
createDescription(builder);
createClickHandler(builder, template.actions());
return builder.build();
}
private void createClickHandler(PanelItemBuilder builder, @NonNull List<ActionRecords> actions) {
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (actions.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.LEFT)) {
rankView = RanksManager.getInstance().getRankDownValue(rankView);
if (rankView <= RanksManager.VISITOR_RANK) {
rankView = RanksManager.OWNER_RANK;
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
} else {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
}
}
if (clickType.equals(ClickType.RIGHT)) {
rankView = RanksManager.getInstance().getRankUpValue(rankView);
if (rankView >= RanksManager.OWNER_RANK) {
rankView = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
} else {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
}
}
// Update panel after click
build();
return true;
});
}
private void createDescription(PanelItemBuilder builder) {
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
if (rankView == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK
&& score <= RanksManager.OWNER_RANK) {
builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+ user.getTranslation(reference));
} else if (score > RanksManager.VISITOR_RANK && score < rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score <= RanksManager.OWNER_RANK && score > rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score == rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+ user.getTranslation(reference));
}
});
builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description"));
}
/**
* Create invited button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
if (parent.isInvited(user.getUniqueId()) && user.hasPermission(parent.getAcceptCommand().getPermission())) {
TeamInvite invite = parent.getInvite(user.getUniqueId());
if (invite == null) {
return this.getBlankBorder();
}
User inviter = User.getInstance(invite.getInviter());
String name = inviter.getName();
builder.icon(inviter.getName());
builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation"));
createInviteDescription(builder, invite.getType(), name, template.actions());
createInviteClickHandler(builder, invite, template.actions());
} else {
return this.getBlankBorder();
}
return builder.build();
}
private void createInviteClickHandler(PanelItemBuilder builder, TeamInvite invite,
@NonNull List<ActionRecords> list) {
Type type = invite.getType();
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.SHIFT_LEFT)
&& user.hasPermission(parent.getAcceptCommand().getPermission())) {
plugin.log("Invite accepted: " + user.getName() + " accepted " + type);
// Accept
switch (type) {
case COOP -> parent.getAcceptCommand().acceptCoopInvite(user, invite);
case TRUST -> parent.getAcceptCommand().acceptTrustInvite(user, invite);
default -> parent.getAcceptCommand().acceptTeamInvite(user, invite);
}
user.closeInventory();
}
if (clickType.equals(ClickType.SHIFT_RIGHT)
&& user.hasPermission(parent.getRejectCommand().getPermission())) {
// Reject
plugin.log("Invite rejected: " + user.getName() + " rejected " + type + " invite.");
parent.getRejectCommand().execute(user, "", List.of());
user.closeInventory();
}
return true;
});
}
private void createInviteDescription(PanelItemBuilder builder, Type type, String name,
@NonNull List<ActionRecords> list) {
builder.description(switch (type) {
case COOP -> List.of(
user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name));
case TRUST -> List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust",
TextVariables.NAME, name));
default ->
List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, name),
user.getTranslation("commands.island.team.invite.accept.confirmation"));
});
// Add all the tool tips
builder.description(list.stream()
.map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " "
+ user.getTranslation(ar.tooltip()))
.toList());
}
/**
* Create status button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
// Player issuing the command must have an island
Island is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId());
if (is == null) {
return getBlankBorder();
}
return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
.description(showMembers()).build();
}
private PanelItem getBlankBorder() {
return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
.name((Objects.requireNonNullElse(border.title(), ""))).build();
}
private PanelItem getBlankBackground() {
return new PanelItemBuilder()
.icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER)))
.name((Objects.requireNonNullElse(background.title(), ""))).build();
}
/**
* Create member button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
// Player issuing the command must have an island
Island is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId());
if (is == null) {
return this.getBlankBackground();
}
int minimumRank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); // Get the rank above Visitor.
Optional<User> opMember = is.getMemberSet(minimumRank).stream().map(User::getInstance)
.filter((User usr) -> rankView == RanksManager.OWNER_RANK || is.getRank(usr) == rankView) // If rankView is owner then show all ranks
.sorted(Comparator.comparingInt((User usr) -> is.getRank(usr)).reversed()) // Show owner on left, then descending ranks
.skip(slot.slot()) // Get the head for this slot
.limit(1L).findFirst(); // Get just one head
if (opMember.isEmpty()) {
return this.getBlankBackground();
}
User member = opMember.get();
int rank = is.getRank(member);
String rankRef = RanksManager.getInstance().getRank(rank);
@NonNull
List<ActionRecords> actions = template.actions();
// Make button description depending on viewer
List<String> desc = new ArrayList<>();
int userRank = Objects.requireNonNull(is).getRank(user);
// Add the tooltip for kicking
if (user.hasPermission(parent.getKickCommand().getPermission())
&& userRank >= is.getRankCommand(parent.getLabel() + " kick") && !user.equals(member)) {
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick"))
.map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME)
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
// Set Owner
if (user.hasPermission(parent.getSetOwnerCommand().getPermission()) && !user.equals(member)
&& userRank >= RanksManager.OWNER_RANK && rank >= RanksManager.MEMBER_RANK) {
// Add the tooltip for setowner
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner"))
.map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME)
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
// Leave
if (user.hasPermission(parent.getLeaveCommand().getPermission()) && user.equals(member)
&& userRank < RanksManager.OWNER_RANK) {
// Add the tooltip for leave
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("leave"))
.map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME)
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
if (member.isOnline()) {
desc.add(0, user.getTranslation(rankRef));
return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName()).description(desc)
.clickHandler(
(panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions))
.build();
} else {
// Offline player
desc.add(0, user.getTranslation(rankRef));
return new PanelItemBuilder().icon(member.getName())
.name(offlinePlayerStatus(Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc)
.clickHandler(
(panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions))
.build();
}
}
/**
* Click listener
* @param panel panel
* @param clickingUser clicking user
* @param clickType click type
* @param i slot
* @param target target user
* @param actions actions
* @return true if the inventory item should not be removed - always true
*/
private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target,
List<ActionRecords> actions) {
if (actions.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
int rank = Objects.requireNonNull(island).getRank(clickingUser);
for (ItemTemplateRecord.ActionRecords action : actions) {
if (clickType.equals(action.clickType())) {
switch (action.actionType().toUpperCase(Locale.ENGLISH)) {
case "KICK" -> kickPlayer(clickingUser, target, rank);
case "SETOWNER" -> setOwner(clickingUser, target);
case "LEAVE" -> leave(clickingUser, target);
default -> {
// Do nothing
}
}
}
}
return true;
}
private void leave(User clickingUser, User target) {
if (clickingUser.hasPermission(parent.getLeaveCommand().getPermission()) && target.equals(clickingUser)
&& !clickingUser.getUniqueId().equals(island.getOwner())) {
plugin.log("Leave: " + clickingUser.getName() + " trying to leave island at " + island.getCenter());
clickingUser.closeInventory();
if (parent.getLeaveCommand().leave(clickingUser)) {
plugin.log("Leave: success");
} else {
plugin.log("Leave: failed");
}
}
}
private void setOwner(User clickingUser, User target) {
// Make the player the leader of the island
if (clickingUser.hasPermission(parent.getSetOwnerCommand().getPermission()) && !target.equals(clickingUser)
&& clickingUser.getUniqueId().equals(island.getOwner())
&& island.getRank(target) >= RanksManager.MEMBER_RANK) {
plugin.log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName()
+ " owner of island at " + island.getCenter());
clickingUser.closeInventory();
if (parent.getSetOwnerCommand().setOwner(clickingUser, target.getUniqueId())) {
plugin.log("Set Owner: success");
} else {
plugin.log("Set Owner: failed");
}
}
}
private void kickPlayer(User clickingUser, User target, int rank) {
// Kick the player, or uncoop, or untrust
if (clickingUser.hasPermission(parent.getKickCommand().getPermission()) && !target.equals(clickingUser)
&& rank >= island.getRankCommand(parent.getLabel() + " kick")) {
plugin.log("Kick: " + clickingUser.getName() + " kicked " + target.getName() + " from island at "
+ island.getCenter());
clickingUser.closeInventory();
if (removePlayer(clickingUser, target)) {
clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
plugin.log("Kick: success");
} else {
plugin.log("Kick: failed");
}
}
}
private boolean removePlayer(User clicker, User member) {
// If member then kick, if coop, uncoop, if trusted, then untrust
return switch (island.getRank(member)) {
case RanksManager.COOP_RANK -> parent.getUncoopCommand().unCoopCmd(user, member.getUniqueId());
case RanksManager.TRUSTED_RANK -> parent.getUnTrustCommand().unTrustCmd(user, member.getUniqueId());
default -> {
if (parent.getKickCommand().canExecute(user, parent.getKickCommand().getLabel(),
List.of(member.getName()))) {
yield parent.getKickCommand().kick(clicker, member.getUniqueId());
} else {
yield false;
}
}
};
}
private List<String> showMembers() {
List<String> message = new ArrayList<>();
// Gather online members
long onlineMemberCount = island.getMemberSet(RanksManager.MEMBER_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.count();
// Show header:
message.add(user.getTranslation("commands.island.team.info.header", "[max]",
String.valueOf(plugin.getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)), "[total]",
String.valueOf(island.getMemberSet().size()), "[online]", String.valueOf(onlineMemberCount)));
// We now need to get all online "members" of the island - incl. Trusted and coop
List<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.toList();
for (int rank : RANKS) {
Set<UUID> players = island.getMemberSet(rank, false);
if (!players.isEmpty()) {
if (rank == RanksManager.OWNER_RANK) {
// Slightly special handling for the owner rank
message.add(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
user.getTranslation(RanksManager.OWNER_RANK_REF)));
} else {
message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)), TextVariables.NUMBER,
String.valueOf(island.getMemberSet(rank, false).size())));
}
message.addAll(displayOnOffline(rank, island, onlineMembers));
}
}
return message;
}
private List<String> displayOnOffline(int rank, Island island, List<UUID> onlineMembers) {
List<String> message = new ArrayList<>();
for (UUID member : island.getMemberSet(rank, false)) {
message.add(getMemberStatus(member, onlineMembers.contains(member)));
}
return message;
}
private String getMemberStatus(UUID member, boolean online) {
OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
if (online) {
return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME,
offlineMember.getName());
} else {
return offlinePlayerStatus(offlineMember);
}
}
/**
* Creates text to describe the status of the player
* @param offlineMember member of the team
* @return string
*/
private String offlinePlayerStatus(OfflinePlayer offlineMember) {
String lastSeen = lastSeen(offlineMember);
if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) {
return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME,
offlineMember.getName(), "[last_seen]", lastSeen);
} else {
// This will prevent anyone that is trusted or below to not have a last-seen status
return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen",
TextVariables.NAME, offlineMember.getName());
}
}
private String lastSeen(OfflinePlayer offlineMember) {
// A bit of handling for the last joined date
Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
Instant now = Instant.now();
Duration duration = Duration.between(lastJoined, now);
String lastSeen;
final String reference = "commands.island.team.info.last-seen.layout";
if (duration.toMinutes() < 60L) {
lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toMinutes()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.minutes"));
} else if (duration.toHours() < 24L) {
lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toHours()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours"));
} else {
lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()),
TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days"));
}
return lastSeen;
}
}

View File

@ -5,13 +5,14 @@ import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
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.database.objects.TeamInvite;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -22,8 +23,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
private static final String INVALID_INVITE = "commands.island.team.invite.errors.invalid-invite";
private final IslandTeamCommand itc;
private UUID playerUUID;
private UUID prospectiveOwnerUUID;
public IslandTeamInviteAcceptCommand(IslandTeamCommand islandTeamCommand) {
super(islandTeamCommand, "accept");
@ -39,19 +38,19 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
playerUUID = user.getUniqueId();
UUID playerUUID = user.getUniqueId();
// Check if player has been invited
if (!itc.isInvited(playerUUID)) {
user.sendMessage("commands.island.team.invite.errors.none-invited-you");
return false;
}
// Get the island owner
prospectiveOwnerUUID = itc.getInviter(playerUUID);
UUID prospectiveOwnerUUID = itc.getInviter(playerUUID);
if (prospectiveOwnerUUID == null) {
user.sendMessage(INVALID_INVITE);
return false;
}
Invite invite = itc.getInvite(playerUUID);
TeamInvite invite = itc.getInvite(playerUUID);
if (invite.getType().equals(Type.TEAM)) {
// Check rank to of inviter
Island island = getIslands().getIsland(getWorld(), prospectiveOwnerUUID);
@ -63,16 +62,14 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
// Check if player is already in a team
if (getIslands().inTeam(getWorld(), playerUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), playerUUID)) {
user.sendMessage("commands.island.team.invite.errors.you-already-are-in-team");
return false;
}
// Fire event so add-ons can run commands, etc.
IslandBaseEvent e = TeamEvent.builder()
.island(getIslands().getIsland(getWorld(), prospectiveOwnerUUID))
.reason(TeamEvent.Reason.JOIN)
.involvedPlayer(playerUUID)
.build();
IslandBaseEvent e = TeamEvent.builder().island(getIslands().getIsland(getWorld(), prospectiveOwnerUUID))
.reason(TeamEvent.Reason.JOIN).involvedPlayer(playerUUID).build();
return !e.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(e.isCancelled());
}
@ -82,120 +79,130 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Get the invite
Invite invite = itc.getInvite(playerUUID);
TeamInvite invite = itc.getInvite(user.getUniqueId());
switch (invite.getType()) {
case COOP -> askConfirmation(user, () -> acceptCoopInvite(user, invite));
case TRUST -> askConfirmation(user, () -> acceptTrustInvite(user, invite));
default -> askConfirmation(user, user.getTranslation("commands.island.team.invite.accept.confirmation"),
() -> acceptTeamInvite(user, invite));
default -> {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
askConfirmation(user, user.getTranslation("commands.island.team.invite.accept.confirmation"),
() -> acceptTeamInvite(user, invite));
} else {
acceptTeamInvite(user, invite);
}
}
}
return true;
}
private void acceptTrustInvite(User user, Invite invite) {
void acceptTrustInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(playerUUID);
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
Island island = getIslands().getIsland(getWorld(), inviter);
if (island != null) {
if (island.getMemberSet(RanksManager.TRUSTED_RANK, false).size() > getIslands().getMaxMembers(island, RanksManager.TRUSTED_RANK)) {
user.sendMessage("commands.island.team.trust.is-full");
return;
}
island.setRank(user, RanksManager.TRUSTED_RANK);
IslandEvent.builder()
.island(island)
.involvedPlayer(user.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(user), RanksManager.TRUSTED_RANK)
.build();
inviter.sendMessage("commands.island.team.trust.success", TextVariables.NAME, user.getName());
user.sendMessage("commands.island.team.trust.you-are-trusted", TextVariables.NAME, inviter.getName());
Island island = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (island != null) {
if (island.getMemberSet(RanksManager.TRUSTED_RANK, false).size() > getIslands().getMaxMembers(island,
RanksManager.TRUSTED_RANK)) {
user.sendMessage("commands.island.team.trust.is-full");
return;
}
island.setRank(user, RanksManager.TRUSTED_RANK);
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(island.getRank(user), RanksManager.TRUSTED_RANK)
.build();
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.trust.success", TextVariables.NAME, user.getName(),
TextVariables.DISPLAY_NAME, user.getDisplayName());
}
if (inviter.isPlayer()) {
user.sendMessage("commands.island.team.trust.you-are-trusted", TextVariables.NAME, inviter.getName(),
TextVariables.DISPLAY_NAME, inviter.getDisplayName());
}
}
}
private void acceptCoopInvite(User user, Invite invite) {
void acceptCoopInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(playerUUID);
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
Island island = getIslands().getIsland(getWorld(), inviter);
if (island != null) {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() > getIslands().getMaxMembers(island, RanksManager.COOP_RANK)) {
user.sendMessage("commands.island.team.coop.is-full");
return;
}
island.setRank(user, RanksManager.COOP_RANK);
IslandEvent.builder()
.island(island)
.involvedPlayer(user.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(user), RanksManager.COOP_RANK)
.build();
inviter.sendMessage("commands.island.team.coop.success", TextVariables.NAME, user.getName());
user.sendMessage("commands.island.team.coop.you-are-a-coop-member", TextVariables.NAME, inviter.getName());
Island island = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (island != null) {
if (island.getMemberSet(RanksManager.COOP_RANK, false).size() > getIslands().getMaxMembers(island,
RanksManager.COOP_RANK)) {
user.sendMessage("commands.island.team.coop.is-full");
return;
}
island.setRank(user, RanksManager.COOP_RANK);
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(island.getRank(user), RanksManager.COOP_RANK)
.build();
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.coop.success", TextVariables.NAME, user.getName(),
TextVariables.DISPLAY_NAME, user.getDisplayName());
}
if (inviter.isPlayer()) {
user.sendMessage("commands.island.team.coop.you-are-a-coop-member", TextVariables.NAME,
inviter.getName(), TextVariables.DISPLAY_NAME, inviter.getDisplayName());
}
}
}
private void acceptTeamInvite(User user, Invite invite) {
void acceptTeamInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(playerUUID);
itc.removeInvite(user.getUniqueId());
// Get the player's island - may be null if the player has no island
Island island = getIslands().getIsland(getWorld(), playerUUID);
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
// Get the team's island
Island teamIsland = getIslands().getIsland(getWorld(), prospectiveOwnerUUID);
Island teamIsland = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (teamIsland == null) {
user.sendMessage(INVALID_INVITE);
return;
}
if (teamIsland.getMemberSet(RanksManager.MEMBER_RANK, true).size() > getIslands().getMaxMembers(teamIsland, RanksManager.MEMBER_RANK)) {
if (teamIsland.getMemberSet(RanksManager.MEMBER_RANK, true).size() >= getIslands().getMaxMembers(teamIsland,
RanksManager.MEMBER_RANK)) {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return;
}
// Remove player as owner of the old island
getIslands().removePlayer(getWorld(), playerUUID);
// Remove money inventory etc. for leaving
cleanPlayer(user);
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
// Remove the player's other islands
getIslands().removePlayer(getWorld(), user.getUniqueId());
// Remove money inventory etc. for leaving
cleanPlayer(user);
}
// Add the player as a team member of the new island
getIslands().setJoinTeam(teamIsland, playerUUID);
getIslands().setJoinTeam(teamIsland, user.getUniqueId());
// Move player to team's island
getIslands().setPrimaryIsland(user.getUniqueId(), teamIsland);
getIslands().homeTeleportAsync(getWorld(), user.getPlayer()).thenRun(() -> {
// Delete the old island
if (island != null) {
getIslands().deleteIsland(island, true, user.getUniqueId());
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()) {
// Delete the old islands
islands.forEach(island -> getIslands().deleteIsland(island, true, user.getUniqueId()));
}
// Put player back into normal mode
user.setGameMode(getIWM().getDefaultGameMode(getWorld()));
// Execute commands
String ownerName = this.getPlayers().getName(teamIsland.getOwner());
Util.runCommands(user, ownerName, getIWM().getOnJoinCommands(getWorld()), "join");
});
// Reset deaths
if (getIWM().isTeamJoinDeathReset(getWorld())) {
getPlayers().setDeaths(getWorld(), playerUUID, 0);
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIWM().isTeamJoinDeathReset(getWorld())) {
// Reset deaths
getPlayers().setDeaths(getWorld(), user.getUniqueId(), 0);
}
user.sendMessage("commands.island.team.invite.accept.you-joined-island", TextVariables.LABEL, getTopLabel());
User inviter = User.getInstance(invite.getInviter());
if (inviter != null) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName());
if (inviter.isOnline()) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
}
getIslands().save(teamIsland);
// Fire event
TeamEvent.builder()
.island(getIslands().getIsland(getWorld(), prospectiveOwnerUUID))
.reason(TeamEvent.Reason.JOINED)
.involvedPlayer(playerUUID)
.build();
IslandEvent.builder()
.island(teamIsland)
.involvedPlayer(user.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(teamIsland.getRank(user), RanksManager.MEMBER_RANK)
.build();
TeamEvent.builder().island(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(user.getUniqueId())
.build();
IslandEvent.builder().island(teamIsland).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(teamIsland.getRank(user), RanksManager.MEMBER_RANK)
.build();
}
private void cleanPlayer(User user) {
@ -205,7 +212,8 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
if (getIWM().isOnLeaveResetInventory(getWorld()) || getIWM().isOnJoinResetInventory(getWorld())) {
user.getPlayer().getInventory().clear();
}
if (getSettings().isUseEconomy() && (getIWM().isOnLeaveResetMoney(getWorld()) || getIWM().isOnJoinResetMoney(getWorld()))) {
if (getSettings().isUseEconomy()
&& (getIWM().isOnLeaveResetMoney(getWorld()) || getIWM().isOnJoinResetMoney(getWorld()))) {
getPlugin().getVault().ifPresent(vault -> vault.withdraw(user, vault.getBalance(user)));
}
@ -221,10 +229,12 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
// Reset the XP
if (getIWM().isOnJoinResetXP(getWorld())) {
// Player collected XP (displayed)
user.getPlayer().setLevel(0);
user.getPlayer().setExp(0);
// Player total XP (not displayed)
user.getPlayer().setTotalExperience(0);
}
// Execute commands
Util.runCommands(user, getIWM().getOnJoinCommands(getWorld()), "join");
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -9,12 +10,15 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -22,6 +26,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
private final IslandTeamCommand itc;
private @Nullable User invitedPlayer;
private @Nullable TemplateItem border;
private @Nullable TemplateItem background;
public IslandTeamInviteCommand(IslandTeamCommand parent) {
super(parent, "invite");
@ -34,90 +40,124 @@ public class IslandTeamInviteCommand extends CompositeCommand {
setOnlyPlayer(true);
setDescription("commands.island.team.invite.description");
setConfigurableRankCommand();
// Panels
if (!new File(getPlugin().getDataFolder() + File.separator + "panels", "team_invite_panel.yml").exists()) {
getPlugin().saveResource("panels/team_invite_panel.yml", false);
}
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
UUID playerUUID = user.getUniqueId();
IslandsManager islandsManager = getIslands();
// Player issuing the command must have an island or be in a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId()) && !getIslands().hasIsland(getWorld(), user.getUniqueId())) {
if (!islandsManager.inTeam(getWorld(), playerUUID) && !islandsManager.hasIsland(getWorld(), playerUUID)) {
user.sendMessage("general.errors.no-island");
return false;
}
UUID playerUUID = user.getUniqueId();
Island island = islandsManager.getIsland(getWorld(), user);
if (args.size() != 1) {
// Invite label with no name, i.e., /island invite - tells the player who has invited them so far and why
if (itc.isInvited(playerUUID)) {
Invite invite = itc.getInvite(playerUUID);
String name = getPlayers().getName(playerUUID);
switch (invite.getType()) {
case COOP -> user.sendMessage("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name);
case TRUST -> user.sendMessage("commands.island.team.invite.name-has-invited-you.trust", TextVariables.NAME, name);
default -> user.sendMessage("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, name);
}
return true;
}
// Show help
showHelp(this, user);
return false;
new IslandTeamInviteGUI(itc, true, island).build(user);
return true;
}
// Check rank to use command
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
return checkRankAndInvitePlayer(user, island, rank, args.get(0));
}
private boolean checkRankAndInvitePlayer(User user, Island island, int rank, String playerName) {
PlayersManager playersManager = getPlayers();
UUID playerUUID = user.getUniqueId();
// Check rank to use command
int requiredRank = island.getRankCommand(getUsage());
if (rank < requiredRank) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Check for space on team
if (island.getMemberSet().size() > getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)) {
int maxMembers = getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK);
if (island.getMemberSet().size() >= maxMembers) {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return false;
}
UUID invitedPlayerUUID = getPlayers().getUUID(args.get(0));
UUID invitedPlayerUUID = playersManager.getUUID(playerName);
if (invitedPlayerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, playerName);
return false;
}
// Only online players can be invited
// Write to field as this is used by execute method
invitedPlayer = User.getInstance(invitedPlayerUUID);
if (!canInvitePlayer(user, invitedPlayer)) {
return false;
}
// Check cooldown
if (this.getSettings().getInviteCooldown() > 0 && checkCooldown(user, island.getUniqueId(), invitedPlayerUUID.toString())) {
return false;
}
// Player cannot invite someone already on a team
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}
if (isInvitedByUser(invitedPlayerUUID, playerUUID) && isInviteTypeTeam(invitedPlayerUUID)) {
user.sendMessage("commands.island.team.invite.errors.you-have-already-invited");
return false;
}
return true;
}
private boolean canInvitePlayer(User user, User invitedPlayer) {
UUID playerUUID = user.getUniqueId();
if (!invitedPlayer.isOnline() || !user.getPlayer().canSee(invitedPlayer.getPlayer())) {
user.sendMessage("general.errors.offline-player");
return false;
}
// Player cannot invite themselves
if (playerUUID.equals(invitedPlayerUUID)) {
if (playerUUID.equals(invitedPlayer.getUniqueId())) {
user.sendMessage("commands.island.team.invite.errors.cannot-invite-self");
return false;
}
// Check cool down
if (getSettings().getInviteCooldown() > 0 && checkCooldown(user, getIslands().getIsland(getWorld(), user).getUniqueId(), invitedPlayerUUID.toString())) {
return false;
}
// Player cannot invite someone already on a team
if (getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}
if (itc.isInvited(invitedPlayerUUID) && itc.getInviter(invitedPlayerUUID).equals(user.getUniqueId()) && itc.getInvite(invitedPlayerUUID).getType().equals(Type.TEAM)) {
// Prevent spam
user.sendMessage("commands.island.team.invite.errors.you-have-already-invited");
return false;
}
return true;
}
private boolean isInvitedByUser(UUID invitedPlayerUUID, UUID inviterUUID) {
return itc.isInvited(invitedPlayerUUID) && itc.getInviter(invitedPlayerUUID).equals(inviterUUID);
}
private boolean isInviteTypeTeam(UUID invitedPlayerUUID) {
return Objects.requireNonNull(itc.getInvite(invitedPlayerUUID)).getType().equals(Type.TEAM);
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Rare case when invited player is null. Could be a race condition.
if (invitedPlayer == null) return false;
// If that player already has an invite out then retract it.
// Players can only have one invite one at a time - interesting
if (itc.isInvited(invitedPlayer.getUniqueId())) {
itc.removeInvite(invitedPlayer.getUniqueId());
user.sendMessage("commands.island.team.invite.removing-invite");
}
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
}
// Fire event so add-ons can run commands, etc.
IslandBaseEvent e = TeamEvent.builder()
.island(getIslands().getIsland(getWorld(), user.getUniqueId()))
.island(island)
.reason(TeamEvent.Reason.INVITE)
.involvedPlayer(invitedPlayer.getUniqueId())
.build();
@ -126,12 +166,13 @@ public class IslandTeamInviteCommand extends CompositeCommand {
}
// Put the invited player (key) onto the list with inviter (value)
// If someone else has invited a player, then this invite will overwrite the previous invite!
itc.addInvite(Invite.Type.TEAM, user.getUniqueId(), invitedPlayer.getUniqueId());
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, invitedPlayer.getName());
itc.addInvite(Type.TEAM, user.getUniqueId(), invitedPlayer.getUniqueId(), island);
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, invitedPlayer.getName(), TextVariables.DISPLAY_NAME, invitedPlayer.getDisplayName());
// Send message to online player
invitedPlayer.sendMessage("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, user.getName());
invitedPlayer.sendMessage("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
invitedPlayer.sendMessage("commands.island.team.invite.to-accept-or-reject", TextVariables.LABEL, getTopLabel());
if (getIslands().hasIsland(getWorld(), invitedPlayer.getUniqueId())) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().hasIsland(getWorld(), invitedPlayer.getUniqueId())) {
invitedPlayer.sendMessage("commands.island.team.invite.you-will-lose-your-island");
}
return true;

View File

@ -0,0 +1,286 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
public class IslandTeamInviteGUI {
private final IslandTeamInviteCommand itic;
private final IslandTeamCommand itc;
private @Nullable TemplateItem border;
private @Nullable TemplateItem background;
private User user;
private long page = 0; // This number by 35
private final boolean inviteCmd;
private static final long PER_PAGE = 35;
private String searchName = "";
private final BentoBox plugin;
private final Island island;
public IslandTeamInviteGUI(IslandTeamCommand itc, boolean invitedCmd, Island island) {
this.island = island;
this.plugin = itc.getPlugin();
this.inviteCmd = invitedCmd;
itic = itc.getInviteCommand();
this.itc = itc;
// Panels
if (!new File(plugin.getDataFolder() + File.separator + "panels", "team_invite_panel.yml")
.exists()) {
plugin.saveResource("panels/team_invite_panel.yml", false);
}
}
/**
* Build the invite panel
* @param user use of the panel
*/
void build(User user) {
this.user = user;
// Start building panel.
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(user);
panelBuilder.world(user.getWorld());
panelBuilder.template("team_invite_panel", new File(plugin.getDataFolder(), "panels"));
panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName());
panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton);
panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton);
panelBuilder.registerTypeBuilder("BACK", this::createBackButton);
// Stash the backgrounds for later use
border = panelBuilder.getPanelTemplate().border();
background = panelBuilder.getPanelTemplate().background();
// Register unknown type builder.
panelBuilder.build();
}
private PanelItem createBackButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
checkTemplate(template);
return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
.clickHandler((panel, user, clickType, clickSlot) -> {
user.closeInventory();
if (!inviteCmd) {
new IslandTeamGUI(plugin, itc, user, island).build();
}
return true;
}).build();
}
private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
checkTemplate(template);
PanelItemBuilder pib = new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
.clickHandler((panel, user, clickType, clickSlot) -> {
user.closeInventory();
new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90)
.withModality(false).withFirstPrompt(new InviteNamePrompt())
.buildConversation(user.getPlayer()).begin();
return true;
});
if (!this.searchName.isBlank()) {
pib.description(user.getTranslation(Objects
.requireNonNullElse(template.description(),
"commands.island.team.invite.gui.button.searching"),
TextVariables.NAME, searchName));
}
return pib.build();
}
private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
checkTemplate(template);
long count = itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
.filter(player -> !player.equals(user.getPlayer())).count();
if (count > page * PER_PAGE) {
// We need to show a next button
return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
.clickHandler((panel, user, clickType, clickSlot) -> {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
page++;
build(user);
return true;
}).build();
}
return getBlankBorder();
}
private void checkTemplate(ItemTemplateRecord template) {
if (template.icon() == null) {
plugin.logError("Icon in template is missing or unknown! " + template.toString());
}
if (template.title() == null) {
plugin.logError("Title in template is missing! " + template.toString());
}
}
private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
checkTemplate(template);
if (page > 0) {
// We need to show a next button
return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon())
.clickHandler((panel, user, clickType, clickSlot) -> {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
page--;
build(user);
return true;
}).build();
}
return getBlankBorder();
}
private PanelItem getBlankBorder() {
return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER)))
.name((Objects.requireNonNullElse(border.title(), ""))).build();
}
/**
* Create member button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
// Player issuing the command must have an island
Island is = plugin.getIslands().getPrimaryIsland(itc.getWorld(), user.getUniqueId());
if (is == null) {
return this.getBlankBackground();
}
if (page < 0) {
page = 0;
}
// Stream of all players that the user can see
Stream<Player> visiblePlayers = itc.getWorld().getPlayers().stream().filter(user.getPlayer()::canSee);
// Filter players based on searchName if it's not blank, and ensure they're not the user
Stream<Player> filteredPlayers = visiblePlayers
.filter(player -> this.searchName.isBlank()
|| player.getName().toLowerCase().contains(searchName.toLowerCase()))
.filter(player -> !player.equals(user.getPlayer()));
// Skipping to the correct pagination slot, then finding the first player
Optional<Player> playerOptional = filteredPlayers.skip(slot.slot() + page * PER_PAGE).findFirst();
// Map the player to a prospect or return a blank background if not found
return playerOptional.map(player -> getProspect(player, template)).orElse(this.getBlankBackground());
}
private PanelItem getProspect(Player player, ItemTemplateRecord template) {
// Check if the prospect has already been invited
if (this.itc.isInvited(player.getUniqueId())
&& user.getUniqueId().equals(this.itc.getInvite(player.getUniqueId()).getInviter())) {
return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
.description(user.getTranslation("commands.island.team.invite.gui.button.already-invited")).build();
}
List<String> desc = template.actions().stream().map(ar -> user
.getTranslation("commands.island.team.invite.gui.tips." + ar.clickType().name() + ".name")
+ " " + user.getTranslation(ar.tooltip())).toList();
return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()).description(desc)
.clickHandler(
(panel, user, clickType, clickSlot) -> clickHandler(user, clickType, player,
template.actions()))
.build();
}
private boolean clickHandler(User user, ClickType clickType, Player player, @NonNull List<ActionRecords> list) {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.LEFT)) {
user.closeInventory();
if (itic.canExecute(user, itic.getLabel(), List.of(player.getName()))) {
plugin.log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in "
+ itc.getWorld().getName());
itic.execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in "
+ itc.getWorld().getName());
}
} else if (clickType.equals(ClickType.RIGHT)) {
user.closeInventory();
if (this.itc.getCoopCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) {
plugin.log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in "
+ itc.getWorld().getName());
this.itc.getCoopCommand().execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log(
"Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in "
+ itc.getWorld().getName());
}
} else if (clickType.equals(ClickType.SHIFT_LEFT)) {
user.closeInventory();
if (this.itc.getTrustCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) {
plugin.log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in "
+ itc.getWorld().getName());
this.itc.getTrustCommand().execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName()
+ " for island in "
+ itc.getWorld().getName());
}
}
return true;
}
private PanelItem getBlankBackground() {
return new PanelItemBuilder()
.icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER)))
.name((Objects.requireNonNullElse(background.title(), ""))).build();
}
class InviteNamePrompt extends StringPrompt {
@Override
@NonNull
public String getPromptText(@NonNull ConversationContext context) {
return user.getTranslation("commands.island.team.invite.gui.enter-name");
}
@Override
public Prompt acceptInput(@NonNull ConversationContext context, String input) {
if (itic.canExecute(user, itic.getLabel(), List.of(input))
&& itic.execute(user, itic.getLabel(), List.of(input))) {
return Prompt.END_OF_CONVERSATION;
}
// Set the search item to what was entered
searchName = input;
// Return to the GUI but give a second for the error to show
// TODO: return the failed input and display the options in the GUI.
Bukkit.getScheduler().runTaskLater(BentoBox.getInstance(), () -> build(user), 20L);
return Prompt.END_OF_CONVERSATION;
}
}
}

View File

@ -42,9 +42,9 @@ public class IslandTeamInviteRejectCommand extends CompositeCommand {
}
Optional.ofNullable(itc.getInviter(playerUUID))
.map(User::getInstance)
.ifPresent(inviter ->
inviter.sendMessage("commands.island.team.invite.reject.name-rejected-your-invite", TextVariables.NAME, user.getName())
.map(User::getInstance)
.ifPresent(inviter ->
inviter.sendMessage("commands.island.team.invite.reject.name-rejected-your-invite", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName())
);
// Remove this player from the global invite list

View File

@ -4,7 +4,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -20,7 +19,6 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
public class IslandTeamKickCommand extends ConfirmableCommand {
public IslandTeamKickCommand(CompositeCommand islandTeamCommand) {
@ -37,7 +35,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
@ -46,7 +44,8 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// If args are not right, show help
@ -64,18 +63,24 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
user.sendMessage("commands.island.team.kick.cannot-kick");
return false;
}
if (!getIslands().getMembers(getWorld(), user.getUniqueId()).contains(targetUUID)) {
if (!getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("general.errors.not-in-team");
return false;
}
int targetRank = Objects.requireNonNull(island).getRank(targetUUID);
if (rank <= targetRank) {
user.sendMessage("commands.island.team.kick.cannot-kick-rank",
TextVariables.NAME, getPlayers().getName(targetUUID));
user.sendMessage("commands.island.team.kick.cannot-kick-rank", TextVariables.NAME,
getPlayers().getName(targetUUID));
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
// Get target
UUID targetUUID = getPlayers().getUUID(args.get(0));
if (!getSettings().isKickConfirmation()) {
kick(user, targetUUID);
return true;
@ -85,43 +90,41 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
}
}
private void kick(User user, UUID targetUUID) {
User target = User.getInstance(targetUUID);
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be null because of checks above
// Fire event
IslandBaseEvent event = TeamEvent.builder()
.island(oldIsland)
.reason(TeamEvent.Reason.KICK)
.involvedPlayer(targetUUID)
.build();
if (event.isCancelled()) {
return;
protected boolean kick(User user, UUID targetUUID) {
if (targetUUID == null) {
return false;
}
target.sendMessage("commands.island.team.kick.player-kicked",
TextVariables.GAMEMODE, getAddon().getDescription().getName(),
TextVariables.NAME, user.getName());
User target = User.getInstance(targetUUID);
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), user)); // Should never be
// null because of
// checks above
// Fire event
IslandBaseEvent event = TeamEvent.builder().island(oldIsland).reason(TeamEvent.Reason.KICK)
.involvedPlayer(targetUUID).build();
if (event.isCancelled()) {
return false;
}
target.sendMessage("commands.island.team.kick.player-kicked", TextVariables.GAMEMODE,
getAddon().getDescription().getName(), TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME,
user.getDisplayName());
getIslands().removePlayer(getWorld(), targetUUID);
getIslands().removePlayer(oldIsland, targetUUID);
// Clean the target player
getPlayers().cleanLeavingPlayer(getWorld(), target, true);
getPlayers().cleanLeavingPlayer(getWorld(), target, true, oldIsland);
user.sendMessage("commands.island.team.kick.success", TextVariables.NAME, target.getName());
IslandEvent.builder()
.island(oldIsland)
.involvedPlayer(user.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(oldIsland.getRank(user), RanksManager.VISITOR_RANK)
.build();
user.sendMessage("commands.island.team.kick.success", TextVariables.NAME, target.getName(),
TextVariables.DISPLAY_NAME, target.getDisplayName());
IslandEvent.builder().island(oldIsland).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(oldIsland.getRank(user), RanksManager.VISITOR_RANK)
.build();
// Add cooldown for this player and target
if (getSettings().getInviteCooldown() > 0 && getParent() != null) {
// Get the invite class from the parent
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(
oldIsland.getUniqueId(),
targetUUID.toString(),
getSettings().getInviteCooldown() * 60));
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(oldIsland.getUniqueId(),
targetUUID.toString(), getSettings().getInviteCooldown() * 60));
}
return true;
}
@Override
@ -129,11 +132,10 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
if (island != null) {
List<String> options = island.getMemberSet().stream()
.filter(uuid -> island.getRank(uuid) >= RanksManager.MEMBER_RANK)
.map(Bukkit::getOfflinePlayer)
.map(OfflinePlayer::getName).collect(Collectors.toList());
.filter(uuid -> island.getRank(uuid) >= RanksManager.MEMBER_RANK).map(Bukkit::getOfflinePlayer)
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
return Optional.of(Util.tabLimit(options, lastArg));
} else {
return Optional.empty();

View File

@ -32,7 +32,7 @@ public class IslandTeamLeaveCommand extends ConfirmableCommand {
user.sendMessage("general.errors.no-team");
return false;
}
if (getIslands().hasIsland(getWorld(), user.getUniqueId())) {
if (user.getUniqueId().equals(getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getOwner())) {
user.sendMessage("commands.island.team.leave.cannot-leave");
return false;
}
@ -65,29 +65,32 @@ public class IslandTeamLeaveCommand extends ConfirmableCommand {
}
private void leave(User user) {
protected boolean leave(User user) {
Island island = getIslands().getIsland(getWorld(), user);
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
}
// Fire event
IslandBaseEvent event = TeamEvent.builder()
.island(island)
.reason(TeamEvent.Reason.LEAVE)
.involvedPlayer(user.getUniqueId())
.build();
IslandBaseEvent event = TeamEvent.builder().island(island).reason(TeamEvent.Reason.LEAVE)
.involvedPlayer(user.getUniqueId()).build();
if (event.isCancelled()) {
return;
return false;
}
UUID ownerUUID = getIslands().getOwner(getWorld(), user.getUniqueId());
UUID ownerUUID = island.getOwner();
if (ownerUUID != null) {
User.getInstance(ownerUUID).sendMessage("commands.island.team.leave.left-your-island", TextVariables.NAME, user.getName());
User.getInstance(ownerUUID).sendMessage("commands.island.team.leave.left-your-island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
}
getIslands().setLeaveTeam(getWorld(), user.getUniqueId());
getIslands().removePlayer(island, user.getUniqueId());
// Clean the player
getPlayers().cleanLeavingPlayer(getWorld(), user, false);
getPlayers().cleanLeavingPlayer(getWorld(), user, false, island);
// Add cooldown for this player and target
if (getSettings().getInviteCooldown() > 0 && getParent() != null) {
// Get the invite class from the parent
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(island.getUniqueId(), user.getUniqueId().toString(), getSettings().getInviteCooldown() * 60));
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(island.getUniqueId(),
user.getUniqueId().toString(), getSettings().getInviteCooldown() * 60));
}
// Remove reset if required
if (getIWM().isLeaversLoseReset(getWorld())) {
@ -97,12 +100,9 @@ public class IslandTeamLeaveCommand extends ConfirmableCommand {
showResets(user);
}
user.sendMessage("commands.island.team.leave.success");
IslandEvent.builder()
.island(island)
.involvedPlayer(user.getUniqueId())
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(user), RanksManager.VISITOR_RANK)
.build();
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(island.getRank(user), RanksManager.VISITOR_RANK)
.build();
return true;
}
}

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.island.team;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -16,8 +15,13 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Handle promotion and demotion
*/
public class IslandTeamPromoteCommand extends CompositeCommand {
private User target;
public IslandTeamPromoteCommand(CompositeCommand islandTeamCommand, String string) {
super(islandTeamCommand, string);
}
@ -36,54 +40,79 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
this.setConfigurableRankCommand();
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return true;
}
// Check rank to use command
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
return false;
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
showHelp(this, user);
return false;
}
// Get target
User target = getPlayers().getUser(args.get(0));
if (target == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return true;
// Check if the user has a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
}
// Check if the user is not trying to promote/ demote himself
if (target == user) {
user.sendMessage("commands.island.team.demote.errors.cant-demote-yourself");
return true;
}
if (!inTeam(getWorld(), target) || !Objects.requireNonNull(getOwner(getWorld(), user), "Island has no owner!").equals(getOwner(getWorld(), target))) {
user.sendMessage("general.errors.not-in-team");
return true;
// Check rank to use command
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target
target = getPlayers().getUser(args.get(0));
if (target == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Check that target is a member of this island
if (!island.inTeam(target.getUniqueId())) {
user.sendMessage("commands.island.team.promote.errors.must-be-member");
return false;
}
// Check if the user is not trying to promote/ demote himself
if (target.equals(user)) {
if (this.getLabel().equals("promote")) {
user.sendMessage("commands.island.team.promote.errors.cant-promote-yourself");
} else {
user.sendMessage("commands.island.team.demote.errors.cant-demote-yourself");
}
return false;
}
// Check that user is not trying to promote above their own rank
// Check that user is not trying to demote ranks higher than them
if (island.getRank(target) >= island.getRank(user)) {
if (this.getLabel().equals("promote")) {
user.sendMessage("commands.island.team.promote.errors.cant-promote");
} else {
user.sendMessage("commands.island.team.demote.errors.cant-demote");
}
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
return change(user, target);
}
private boolean change(User user, User target) {
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
Island island = getIslands().getIsland(getWorld(), user);
int currentRank = island.getRank(target);
if (this.getLabel().equals("promote")) {
int nextRank = getPlugin().getRanksManager().getRankUpValue(currentRank);
int nextRank = RanksManager.getInstance().getRankUpValue(currentRank);
// Stop short of owner
if (nextRank != RanksManager.OWNER_RANK && nextRank > currentRank) {
getIslands().getIsland(getWorld(), user.getUniqueId()).setRank(target, nextRank);
String rankName = user.getTranslation(getPlugin().getRanksManager().getRank(nextRank));
user.sendMessage("commands.island.team.promote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName);
if (nextRank < RanksManager.OWNER_RANK && currentRank >= RanksManager.MEMBER_RANK
&& nextRank > currentRank) {
island.setRank(target, nextRank);
String rankName = user.getTranslation(RanksManager.getInstance().getRank(nextRank));
user.sendMessage("commands.island.team.promote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName());
IslandEvent.builder()
.island(island)
.involvedPlayer(user.getUniqueId())
@ -98,12 +127,12 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
}
} else {
// Demote
int prevRank = getPlugin().getRanksManager().getRankDownValue(currentRank);
int prevRank = RanksManager.getInstance().getRankDownValue(currentRank);
// Lowest is Member
if (prevRank >= RanksManager.MEMBER_RANK && prevRank < currentRank) {
getIslands().getIsland(getWorld(), user.getUniqueId()).setRank(target, prevRank);
String rankName = user.getTranslation(getPlugin().getRanksManager().getRank(prevRank));
user.sendMessage("commands.island.team.demote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName);
island.setRank(target, prevRank);
String rankName = user.getTranslation(RanksManager.getInstance().getRank(prevRank));
user.sendMessage("commands.island.team.demote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName());
IslandEvent.builder()
.island(island)
.involvedPlayer(user.getUniqueId())
@ -121,11 +150,11 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
Island island = getIslands().getIsland(getWorld(), user.getUniqueId());
Island island = getIslands().getIsland(getWorld(), user);
if (island != null) {
List<String> options = island.getMemberSet().stream()
.map(Bukkit::getOfflinePlayer)
.map(OfflinePlayer::getName).collect(Collectors.toList());
.map(OfflinePlayer::getName).toList();
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(options, lastArg));

View File

@ -4,6 +4,9 @@ import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
@ -16,6 +19,8 @@ import world.bentobox.bentobox.util.Util;
public class IslandTeamSetownerCommand extends CompositeCommand {
private @Nullable UUID targetUUID;
public IslandTeamSetownerCommand(CompositeCommand islandTeamCommand) {
super(islandTeamCommand, "setowner");
}
@ -29,73 +34,76 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
}
@Override
public boolean execute(User user, String label, List<String> args) {
UUID playerUUID = user.getUniqueId();
// Can use if in a team
boolean inTeam = getIslands().inTeam(getWorld(), playerUUID);
if (!inTeam) {
user.sendMessage("general.errors.no-team");
return false;
}
UUID ownerUUID = getOwner(getWorld(), user);
if (ownerUUID == null || !ownerUUID.equals(playerUUID)) {
user.sendMessage("general.errors.not-owner");
return false;
}
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
showHelp(this, user);
return false;
}
UUID targetUUID = getPlayers().getUUID(args.get(0));
// Can use if in a team
Island is = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (is == null || !is.inTeam(user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
}
UUID ownerUUID = is.getOwner();
if (ownerUUID == null || !ownerUUID.equals(user.getUniqueId())) {
user.sendMessage("general.errors.not-owner");
return false;
}
targetUUID = getPlayers().getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (targetUUID.equals(playerUUID)) {
if (targetUUID.equals(user.getUniqueId())) {
user.sendMessage("commands.island.team.setowner.errors.cant-transfer-to-yourself");
return false;
}
if (!getIslands().getMembers(getWorld(), playerUUID).contains(targetUUID)) {
if (!is.inTeam(targetUUID)) {
user.sendMessage("commands.island.team.setowner.errors.target-is-not-member");
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
return setOwner(user, targetUUID);
}
protected boolean setOwner(User user, @NonNull UUID targetUUID2) {
// Fire event so add-ons can run commands, etc.
Island island = getIslands().getIsland(getWorld(), user);
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
// Fire event so add-ons can run commands, etc.
IslandBaseEvent e = TeamEvent.builder()
.island(island)
.reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID)
.build();
IslandBaseEvent e = TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID2).build();
if (e.isCancelled()) {
return false;
}
getIslands().setOwner(getWorld(), user, targetUUID);
getIslands().setOwner(getWorld(), user, targetUUID2);
// Call the event for the new owner
IslandEvent.builder()
.island(island)
.involvedPlayer(targetUUID)
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(User.getInstance(targetUUID)), RanksManager.OWNER_RANK)
.build();
IslandEvent.builder().island(island).involvedPlayer(targetUUID2).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(User.getInstance(targetUUID2)), RanksManager.OWNER_RANK).build();
// Call the event for the previous owner
IslandEvent.builder()
.island(island)
.involvedPlayer(playerUUID)
.admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.OWNER_RANK, island.getRank(user))
.build();
getIslands().save(island);
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK)
.build();
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
return Optional.of(Util.tabLimit(getIslands().getMembers(getWorld(), user.getUniqueId()).stream().map(getPlayers()::getName).toList(), lastArg));
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()) == null) {
return Optional.empty();
}
return Optional.of(Util.tabLimit(
getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().stream()
.filter(uuid -> !user.getUniqueId().equals(uuid)).map(getPlayers()::getName).toList(),
lastArg));
}
}
}

View File

@ -8,10 +8,10 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
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.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -55,7 +55,8 @@ public class IslandTeamTrustCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
if (rank < island.getRankCommand(getUsage())) {
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
user.getTranslation(RanksManager.getInstance().getRank(rank)));
return false;
}
// Get target player
@ -95,10 +96,10 @@ public class IslandTeamTrustCommand extends CompositeCommand {
if (getPlugin().getSettings().isInviteConfirmation()) {
// Put the invited player (key) onto the list with inviter (value)
// If someone else has invited a player, then this invite will overwrite the previous invite!
itc.addInvite(Type.TRUST, user.getUniqueId(), target.getUniqueId());
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, target.getName());
itc.addInvite(Type.TRUST, user.getUniqueId(), target.getUniqueId(), island);
user.sendMessage("commands.island.team.invite.invitation-sent", TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME, target.getDisplayName());
// Send message to online player
target.sendMessage("commands.island.team.trust.name-has-invited-you", TextVariables.NAME, user.getName());
target.sendMessage("commands.island.team.trust.name-has-invited-you", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
target.sendMessage("commands.island.team.invite.to-accept-or-reject", TextVariables.LABEL, getTopLabel());
} else {
if (island.getMemberSet(RanksManager.TRUSTED_RANK, false).size() >= getIslands().getMaxMembers(island, RanksManager.TRUSTED_RANK)) {
@ -107,8 +108,8 @@ public class IslandTeamTrustCommand extends CompositeCommand {
}
island.setRank(target, RanksManager.TRUSTED_RANK);
user.sendMessage("commands.island.team.trust.success", TextVariables.NAME, target.getName());
target.sendMessage("commands.island.team.trust.you-are-trusted", TextVariables.NAME, user.getName());
user.sendMessage("commands.island.team.trust.success", TextVariables.NAME, target.getName(), TextVariables.DISPLAY_NAME, target.getDisplayName());
target.sendMessage("commands.island.team.trust.you-are-trusted", TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName());
}
return true;
} else {

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