Compare commits

...

147 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
258 changed files with 8106 additions and 5502 deletions

View File

@ -13,11 +13,11 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '17'
java-version: '21'
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:

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

79
pom.xml
View File

@ -73,10 +73,10 @@
<postgresql.version>42.2.18</postgresql.version>
<hikaricp.version>5.0.1</hikaricp.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.20.4-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.20.4-R0.1-SNAPSHOT</paper.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>
@ -88,7 +88,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.0.0</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>
@ -145,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>
@ -153,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>
@ -189,6 +189,17 @@
<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>
@ -217,13 +228,37 @@
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<!-- Spigot API -->
<!-- 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>
@ -279,6 +314,12 @@
<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 -->
<dependency>
<groupId>com.github.TheBusyBiscuit</groupId>
@ -324,16 +365,23 @@
<dependency>
<groupId>com.github.Slimefun</groupId>
<artifactId>Slimefun4</artifactId>
<version>RC-36</version>
<version>RC-37</version>
<scope>provided</scope>
</dependency>
<!-- ItemsAdder -->
<dependency>
<groupId>com.github.LoneDev6</groupId>
<artifactId>api-itemsadder</artifactId>
<version>3.6.1</version>
<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>
@ -456,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>
@ -470,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>

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

@ -25,8 +25,10 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
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;
@ -36,6 +38,7 @@ import world.bentobox.bentobox.listeners.DeathListener;
import world.bentobox.bentobox.listeners.JoinLeaveListener;
import world.bentobox.bentobox.listeners.PanelListenerManager;
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;
@ -101,6 +104,8 @@ public class BentoBox extends JavaPlugin implements Listener {
@Override
public void onEnable(){
setInstance(this);
if (!ServerCompatibility.getInstance().checkCompatibility().isCanLaunch()) {
// The server's most likely incompatible.
// Show a warning
@ -122,7 +127,6 @@ public class BentoBox extends JavaPlugin implements Listener {
// Save the default config from config.yml
saveDefaultConfig();
setInstance(this);
// Load Flags
flagsManager = new FlagsManager(this);
@ -183,8 +187,14 @@ public class BentoBox extends JavaPlugin implements Listener {
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);
@ -206,20 +216,6 @@ public class BentoBox extends JavaPlugin implements Listener {
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();
@ -319,6 +315,8 @@ public class BentoBox extends JavaPlugin implements Listener {
manager.registerEvents(islandDeletionManager, this);
// Primary Island Listener
manager.registerEvents(new PrimaryIslandListener(this), this);
// Seed world chunk generator
manager.registerEvents(new SeedWorldMakerListener(this), this);
}
@Override
@ -429,7 +427,7 @@ public class BentoBox extends JavaPlugin implements Listener {
* @return the ranksManager
* @deprecated Just use {@code RanksManager.getInstance()}
*/
@Deprecated(since = "2.0.0")
@Deprecated(since = "2.0.0", forRemoval = true)
public RanksManager getRanksManager() {
return RanksManager.getInstance();
}

View File

@ -195,6 +195,12 @@ public class Settings implements ConfigObject {
@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")
@ -1014,4 +1020,18 @@ public class Settings implements ConfigObject {
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

@ -21,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;
@ -45,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());
}
/**

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;
@ -129,7 +131,10 @@ 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

View File

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

View File

@ -520,6 +520,7 @@ 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

View File

@ -1,10 +1,14 @@
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.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;
@ -16,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");
}
@ -29,56 +36,93 @@ 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;
}
Island island = getIslands().getIsland(getWorld(), user);
if (island == 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) && user.getUniqueId().equals(island.getOwner())) {
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) {
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 deletePlayer(User user) {
// Delete player and island
for (Island oldIsland : getIslands().getIslands(getWorld(), targetUUID)) {
// 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);
deleteIsland(user, oldIsland);
}
// 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)
// Remove target from any and all islands in the world
getIslands().removePlayer(getWorld(), targetUUID);
if (target.isPlayer() && target.isOnline()) {
cleanUp(target);
@ -120,6 +164,31 @@ public class AdminDeleteCommand extends ConfirmableCommand {
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
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
@ -127,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

@ -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

@ -49,9 +49,6 @@ public class AdminInfoCommand extends CompositeCommand {
Island island = getIslands().getIsland(getWorld(), targetUUID);
if (island != null) {
new IslandInfo(island).showAdminInfo(user, getAddon());
if (!getIslands().getQuarantinedIslandByUser(getWorld(), targetUUID).isEmpty()) {
user.sendMessage("commands.admin.info.islands-in-trash");
}
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, RanksManager.VISITOR_RANK);
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, 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();
});
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

@ -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

@ -16,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;
@ -206,11 +205,9 @@ public class AdminSettingsCommand extends CompositeCommand {
switch (f.getType()) {
case PROTECTION -> {
island.setFlag(f, rank);
getIslands().save(island);
}
case SETTING -> {
island.setSettingsFlag(f, activeState);
getIslands().save(island);
}
case WORLD_SETTING -> f.setSetting(getWorld(), activeState);
default -> {
@ -226,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)
@ -242,8 +238,8 @@ public class AdminSettingsCommand extends CompositeCommand {
new TabbedPanelBuilder()
.user(user)
.world(island.getWorld())
.island(island).tab(1, new SettingsTab(user, Flag.Type.PROTECTION))
.tab(2, new SettingsTab(user, 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();

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
@ -124,6 +141,31 @@ public class AdminTeleportCommand extends CompositeCommand {
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
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
@ -131,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

@ -12,7 +12,6 @@ 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;
@ -63,7 +62,6 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminTeamKickCommand(this);
new AdminTeamDisbandCommand(this);
new AdminTeamSetownerCommand(this);
new AdminTeamFixCommand(this);
// Blueprints
new AdminBlueprintCommand(this);
// Register/unregister islands

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) {
@ -99,26 +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);
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

@ -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

@ -40,7 +40,6 @@ public class NamePrompt extends StringPrompt {
@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

@ -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

@ -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");
@ -32,53 +27,28 @@ public class AdminRangeAddCommand extends CompositeCommand {
@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

@ -13,6 +13,7 @@ import org.bukkit.Particle;
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
@ -23,6 +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";
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<>();
@ -76,11 +80,11 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
// 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));
}

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");
@ -32,48 +28,31 @@ public class AdminRangeRemoveCommand extends CompositeCommand {
@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

@ -44,17 +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;
}
Island island = getIslands().getPrimaryIsland(getWorld(), ownerUUID);
if (getIslands().inTeam(getWorld(), ownerUUID) && island != null && !ownerUUID.equals(island.getOwner())) {
if (getIslands().inTeam(getWorld(), ownerUUID) && !ownerUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.team.add.name-not-owner", TextVariables.NAME, args.get(0));
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;
}

View File

@ -32,7 +32,7 @@ public class AdminTeamCommand extends CompositeCommand
new AdminTeamAddCommand(this);
new AdminTeamDisbandCommand(this);
new AdminTeamFixCommand(this);
new AdminTeamKickCommand(this);
new AdminTeamSetownerCommand(this);
}

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,6 +21,13 @@ 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");
}
@ -26,32 +40,57 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
}
@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;
}
Island island = getIslands().getPrimaryIsland(getWorld(), targetUUID);
if (!targetUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.team.disband.use-disband-owner", "[owner]",
getPlayers().getName(island.getOwner()));
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.getMemberSet().forEach(m -> {
User mUser = User.getInstance(m);
@ -68,4 +107,24 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
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.fix");
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

@ -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

@ -1,9 +1,17 @@
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;
@ -17,7 +25,11 @@ import world.bentobox.bentobox.util.Util;
*
* @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");
@ -28,35 +40,50 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
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;
}
Island island = getIslands().getPrimaryIsland(getWorld(), targetUUID);
UUID previousOwnerUUID = island.getOwner();
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
// Call the setowner event
TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER).involvedPlayer(targetUUID).admin(true)
@ -70,8 +97,20 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
.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) {
@ -80,6 +119,13 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
.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

@ -70,7 +70,7 @@ public class IslandBanCommand extends CompositeCommand {
user.sendMessage("commands.island.ban.cannot-ban-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.ban.cannot-ban-member");
return false;
}

View File

@ -51,7 +51,8 @@ public class IslandCreateCommand extends CompositeCommand {
}
}
// Check if this player is on a team in this world
if (getIslands().inTeam(getWorld(), user.getUniqueId()) && island != null
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");
@ -86,22 +87,49 @@ public class IslandCreateCommand extends CompositeCommand {
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
// 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 {

View File

@ -76,7 +76,7 @@ public class IslandExpelCommand extends CompositeCommand {
return false;
}
// Or team member
if (island.getMemberSet().contains(targetUUID)) {
if (island.inTeam(targetUUID)) {
user.sendMessage("commands.island.expel.cannot-expel-member");
return false;
}

View File

@ -6,7 +6,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.DelayedTeleportCommand;
@ -41,7 +40,7 @@ public class IslandGoCommand extends DelayedTeleportCommand {
user.sendMessage("commands.island.go.teleport");
return false;
}
Set<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
if (islands.isEmpty()) {
user.sendMessage("general.errors.no-island");
return false;
@ -86,7 +85,7 @@ public class IslandGoCommand extends DelayedTeleportCommand {
return true;
}
private boolean checkReserved(User user, Set<Island> islands) {
private boolean checkReserved(User user, List<Island> islands) {
for (Island island : islands) {
if (island.isReserved()) {
// Send player to create an island

View File

@ -3,7 +3,6 @@ package world.bentobox.bentobox.api.commands.island;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@ -14,7 +13,7 @@ import world.bentobox.bentobox.util.Util;
public class IslandHomesCommand extends ConfirmableCommand {
private Set<Island> islands;
private List<Island> islands;
public IslandHomesCommand(CompositeCommand islandCommand) {
super(islandCommand, "homes");

View File

@ -111,7 +111,7 @@ public class IslandResetCommand extends ConfirmableCommand {
// 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);
}

View File

@ -53,7 +53,7 @@ public class IslandSethomeCommand extends ConfirmableCommand {
// Check number of homes
int maxHomes = getIslands().getIslands(getWorld(), user).stream().mapToInt(getIslands()::getMaxHomes).sum();
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");

View File

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

View File

@ -49,7 +49,8 @@ public class IslandSettingsCommand extends CompositeCommand {
.user(user)
.island(island)
.world(island.getWorld())
.tab(1, new SettingsTab(user, Flag.Type.PROTECTION)).tab(2, new SettingsTab(user, 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

@ -1,95 +0,0 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.Objects;
import java.util.UUID;
import world.bentobox.bentobox.database.objects.Island;
/**
* 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;
private final Island island;
/**
* @param type - invitation type, e.g., coop, team, trust
* @param inviter - UUID of inviter
* @param invitee - UUID of invitee
* @param island - the island this invite is for
*/
public Invite(Type type, UUID inviter, UUID invitee, Island island) {
this.type = type;
this.inviter = inviter;
this.invitee = invitee;
this.island = island;
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the inviter
*/
public UUID getInviter() {
return inviter;
}
/**
* @return the invitee
*/
public UUID getInvitee() {
return invitee;
}
/**
* @return the island
*/
public Island getIsland() {
return island;
}
/* (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,53 +0,0 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
/**
* Invites a player by search
* @author tastybento
*
*/
public class InviteNamePrompt extends StringPrompt {
@NonNull
private final User user;
@NonNull
private final IslandTeamInviteCommand itic;
public InviteNamePrompt(@NonNull User user, IslandTeamInviteCommand islandTeamInviteCommand) {
this.user = user;
this.itic = islandTeamInviteCommand;
}
@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) {
// TODO remove this and pass the options back to the GUI
if (itic.canExecute(user, itic.getLabel(), List.of(input))) {
if (itic.execute(user, itic.getLabel(), List.of(input))) {
return Prompt.END_OF_CONVERSATION;
}
}
// Set the search item to what was entered
itic.setSearchName(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(), () -> itic.build(user), 20L);
return Prompt.END_OF_CONVERSATION;
}
}

View File

@ -1,24 +1,10 @@
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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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;
@ -26,40 +12,15 @@ 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.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.TemplatedPanel.ItemSlot;
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.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 {
/**
* 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);
/**
* Invited list. Key is the invited party, value is the invite.
* @since 1.8.0
*/
private final Map<UUID, Invite> inviteMap;
private User user;
private Island island;
private int rank = RanksManager.OWNER_RANK;
private IslandTeamKickCommand kickCommand;
private IslandTeamLeaveCommand leaveCommand;
@ -84,9 +45,11 @@ public class IslandTeamCommand extends CompositeCommand {
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
@ -120,13 +83,12 @@ public class IslandTeamCommand extends CompositeCommand {
@Override
public boolean canExecute(User user, String label, List<String> args) {
this.user = user;
// Player issuing the command must have an island
island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
if (isInvited(user.getUniqueId())) {
// Player has an invite, so show the invite
build();
new IslandTeamGUI(getPlugin(), this, user, island).build();
return true;
}
user.sendMessage("general.errors.no-island");
@ -155,511 +117,11 @@ public class IslandTeamCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Show the panel
build();
new IslandTeamGUI(getPlugin(), this, user, getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()))
.build();
return true;
}
/**
* This method builds this GUI.
*/
void build() {
// Start building panel.
TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();
panelBuilder.user(user);
panelBuilder.world(user.getWorld());
panelBuilder.template("team_panel", new File(getPlugin().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.inviteCommand.getPermission())
|| island.getRank(user) < island.getRankCommand(this.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().anyMatch(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();
this.inviteCommand.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
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
if (rank == 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 < rank) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score <= RanksManager.OWNER_RANK && score > rank) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score == rank) {
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"));
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (!template.actions().stream().anyMatch(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)) {
rank = RanksManager.getInstance().getRankDownValue(rank);
if (rank <= RanksManager.VISITOR_RANK) {
rank = 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)) {
rank = RanksManager.getInstance().getRankUpValue(rank);
if (rank >= RanksManager.OWNER_RANK) {
rank = 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;
});
return builder.build();
}
/**
* 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 (isInvited(user.getUniqueId()) && user.hasPermission(this.acceptCommand.getPermission())) {
Invite invite = getInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
String name = inviter.getName();
builder.icon(inviter.getName());
builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation"));
builder.description(switch (invite.getType()) {
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(template.actions().stream()
.map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+ " "
+ user.getTranslation(ar.tooltip()))
.toList());
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (!template.actions().stream().anyMatch(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(this.acceptCommand.getPermission())) {
getPlugin().log("Invite accepted: " + user.getName() + " accepted " + invite.getType()
+ " invite to island at " + island.getCenter());
// Accept
switch (invite.getType()) {
case COOP -> this.acceptCommand.acceptCoopInvite(user, invite);
case TRUST -> this.acceptCommand.acceptTrustInvite(user, invite);
default -> this.acceptCommand.acceptTeamInvite(user, invite);
}
user.closeInventory();
}
if (clickType.equals(ClickType.SHIFT_RIGHT) && user.hasPermission(this.rejectCommand.getPermission())) {
// Reject
getPlugin().log("Invite rejected: " + user.getName() + " rejected " + invite.getType()
+ " invite.");
this.rejectCommand.execute(user, "", List.of());
user.closeInventory();
}
return true;
});
} else {
return this.getBlankBorder();
}
return builder.build();
}
/**
* 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 island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == 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 island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
return this.getBlankBackground();
}
return switch (rank) {
case RanksManager.OWNER_RANK -> ownerView(template, slot);
default -> getMemberButton(rank, slot.slot(), template.actions());
};
}
/**
* The owner view shows all the ranks, in order
* @param template template reference
* @param slot slot to show
* @return panel item
*/
private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) {
if (slot.slot() == 0 && island.getOwner() != null) {
// Owner
PanelItem item = getMemberButton(RanksManager.OWNER_RANK, 1, template.actions());
if (item != null) {
return item;
}
}
long subOwnerCount = island.getMemberSet(RanksManager.SUB_OWNER_RANK, false).stream().count();
long memberCount = island.getMemberSet(RanksManager.MEMBER_RANK, false).stream().count();
long coopCount = island.getMemberSet(RanksManager.COOP_RANK, false).stream().count();
long trustedCount = island.getMemberSet(RanksManager.TRUSTED_RANK, false).stream().count();
if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
// Show sub owners
PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK, slot.slot(), template.actions());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) {
// Show members
PanelItem item = getMemberButton(RanksManager.MEMBER_RANK, slot.slot(), template.actions());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) {
// Show trusted
PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK, slot.slot(), template.actions());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount + memberCount + trustedCount
&& slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
// Show coops
return getMemberButton(RanksManager.COOP_RANK, slot.slot(), template.actions());
}
return this.getBlankBackground();
}
/**
* Shows a member's head. The clicks available will depend on who is viewing.
* @param targetRank - the rank to show
* @param slot - the slot number
* @param actions - actions that need to apply to this member button as provided by the template
* @return panel item
*/
private PanelItem getMemberButton(int targetRank, int slot, List<ActionRecords> actions) {
if (slot == 0 && island.getOwner() != null) {
// Owner
return getMemberButton(RanksManager.OWNER_RANK, 1, actions);
}
String ref = RanksManager.getInstance().getRank(targetRank);
Optional<User> opMember = island.getMemberSet(targetRank, false).stream().sorted().skip(slot - 1L).limit(1L)
.map(User::getInstance).findFirst();
if (opMember.isEmpty()) {
return this.getBlankBackground();
}
User member = opMember.get();
// Make button description depending on viewer
List<String> desc = new ArrayList<>();
int userRank = Objects.requireNonNull(island).getRank(user);
// Add the tooltip for kicking
if (user.hasPermission(this.kickCommand.getPermission())
&& userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) {
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick"))
.map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
// Set Owner
if (user.hasPermission(this.setOwnerCommand.getPermission()) && !user.equals(member)
&& userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) {
// Add the tooltip for setowner
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner"))
.map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
// Leave
if (user.hasPermission(this.leaveCommand.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("commands.island.team.gui.tips." + ar.clickType().name() + ".name")
+ " " + user.getTranslation(ar.tooltip()))
.findFirst().ifPresent(desc::add);
}
if (member.isOnline()) {
desc.add(0, user.getTranslation(ref));
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(ref));
return new PanelItemBuilder().icon(member.getName())
.name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc)
.clickHandler(
(panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions))
.build();
}
}
private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target,
List<ActionRecords> actions) {
if (!actions.stream().anyMatch(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" -> {
// Kick the player, or uncoop, or untrust
if (clickingUser.hasPermission(this.kickCommand.getPermission()) && !target.equals(clickingUser)
&& rank >= island.getRankCommand(this.getLabel() + " kick")) {
getPlugin().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);
getPlugin().log("Kick: success");
} else {
getPlugin().log("Kick: failed");
}
}
}
case "SETOWNER" -> {
// Make the player the leader of the island
if (clickingUser.hasPermission(this.setOwnerCommand.getPermission()) && !target.equals(clickingUser)
&& clickingUser.getUniqueId().equals(island.getOwner())) {
getPlugin().log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName()
+ " owner of island at " + island.getCenter());
clickingUser.closeInventory();
if (this.setOwnerCommand.setOwner(clickingUser, target.getUniqueId())) {
getPlugin().log("Set Owner: success");
} else {
getPlugin().log("Set Owner: failed");
}
}
}
case "LEAVE" -> {
if (clickingUser.hasPermission(this.leaveCommand.getPermission()) && target.equals(clickingUser)
&& !clickingUser.getUniqueId().equals(island.getOwner())) {
getPlugin().log("Leave: " + clickingUser.getName() + " trying to leave island at "
+ island.getCenter());
clickingUser.closeInventory();
if (leaveCommand.leave(clickingUser)) {
getPlugin().log("Leave: success");
} else {
getPlugin().log("Leave: failed");
}
}
}
}
}
}
return true;
}
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 -> this.uncoopCommand.unCoopCmd(user, member.getUniqueId());
case RanksManager.TRUSTED_RANK -> this.unTrustCommand.unTrustCmd(user, member.getUniqueId());
default -> {
if (kickCommand.canExecute(user, kickCommand.getLabel(), List.of(member.getName()))) {
yield kickCommand.execute(user, kickCommand.getLabel(), List.of(member.getName()));
} 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(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(user, rank, island, onlineMembers));
}
}
return message;
}
private List<String> displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
List<String> message = new ArrayList<>();
for (UUID member : island.getMemberSet(rank, false)) {
message.add(getMemberStatus(user, member, onlineMembers.contains(member)));
}
return message;
}
private String getMemberStatus(User user2, 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(user, offlineMember);
}
}
/**
* Creates text to describe the status of the player
* @param user2 user asking to see the status
* @param offlineMember member of the team
* @return string
*/
private String offlinePlayerStatus(User user2, 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;
}
private boolean fireEvent(User user, Island island) {
IslandBaseEvent e = TeamEvent.builder().island(island).reason(TeamEvent.Reason.INFO)
.involvedPlayer(user.getUniqueId()).build();
@ -673,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, @NonNull Island island) {
inviteMap.put(invitee, new Invite(type, inviter, invitee, island));
public void addInvite(TeamInvite.Type type, @NonNull UUID inviter, @NonNull UUID invitee, @NonNull Island island) {
handler.saveObjectAsync(new TeamInvite(type, inviter, invitee, island.getUniqueId()));
}
/**
@ -684,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());
}
/**
@ -695,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;
}
/**
@ -705,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());
}
/**
@ -715,7 +177,7 @@ public class IslandTeamCommand extends CompositeCommand {
* @since 1.8.0
*/
public void removeInvite(@NonNull UUID invitee) {
inviteMap.remove(invitee);
handler.deleteID(invitee.toString());
}
/**
@ -731,4 +193,52 @@ public class IslandTeamCommand extends CompositeCommand {
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,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;
@ -99,7 +99,7 @@ public class IslandTeamCoopCommand 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.COOP, user.getUniqueId(), target.getUniqueId(), island);
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,

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

@ -1,18 +1,18 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.util.List;
import java.util.Set;
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;
@ -50,7 +50,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
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);
@ -62,7 +62,8 @@ 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;
}
@ -78,21 +79,27 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Get the invite
Invite invite = itc.getInvite(user.getUniqueId());
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;
}
void acceptTrustInvite(User user, Invite invite) {
void acceptTrustInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
Island island = invite.getIsland();
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)) {
@ -114,11 +121,11 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
}
void acceptCoopInvite(User user, Invite invite) {
void acceptCoopInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
User inviter = User.getInstance(invite.getInviter());
Island island = invite.getIsland();
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)) {
@ -140,13 +147,13 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
}
}
void acceptTeamInvite(User user, Invite invite) {
void acceptTeamInvite(User user, TeamInvite invite) {
// Remove the invite
itc.removeInvite(user.getUniqueId());
// Get the player's island - may be null if the player has no island
Set<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
List<Island> islands = getIslands().getIslands(getWorld(), user.getUniqueId());
// Get the team's island
Island teamIsland = invite.getIsland();
Island teamIsland = getIslands().getIslandById(invite.getIslandID()).orElse(null);
if (teamIsland == null) {
user.sendMessage(INVALID_INVITE);
return;
@ -156,17 +163,21 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
user.sendMessage("commands.island.team.invite.errors.island-is-full");
return;
}
// Remove player as owner of the old island
getIslands().removePlayer(getWorld(), user.getUniqueId());
// 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, user.getUniqueId());
// Move player to team's island
getIslands().setPrimaryIsland(user.getUniqueId(), teamIsland);
getIslands().homeTeleportAsync(getWorld(), user.getPlayer()).thenRun(() -> {
// Delete the old islands
islands.forEach(island -> 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()));
@ -175,8 +186,9 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
Util.runCommands(user, ownerName, getIWM().getOnJoinCommands(getWorld()), "join");
});
// Reset deaths
if (getIWM().isTeamJoinDeathReset(getWorld())) {
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());
@ -185,7 +197,6 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
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(teamIsland).reason(TeamEvent.Reason.JOINED).involvedPlayer(user.getUniqueId())
.build();

View File

@ -7,31 +7,16 @@ import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.conversations.ConversationFactory;
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.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.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.Type;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.managers.RanksManager;
@ -43,11 +28,6 @@ public class IslandTeamInviteCommand extends CompositeCommand {
private @Nullable User invitedPlayer;
private @Nullable TemplateItem border;
private @Nullable TemplateItem background;
private User user;
private long page = 0; // This number by 35
private boolean inviteCmd;
private static final long PER_PAGE = 35;
private String searchName = "";
public IslandTeamInviteCommand(IslandTeamCommand parent) {
super(parent, "invite");
@ -77,14 +57,13 @@ public class IslandTeamInviteCommand extends CompositeCommand {
user.sendMessage("general.errors.no-island");
return false;
}
Island island = islandsManager.getIsland(getWorld(), user);
if (args.size() != 1) {
this.inviteCmd = true;
build(user);
new IslandTeamInviteGUI(itc, true, island).build(user);
return true;
}
Island island = islandsManager.getIsland(getWorld(), user);
int rank = Objects.requireNonNull(island).getRank(user);
return checkRankAndInvitePlayer(user, island, rank, args.get(0));
@ -126,7 +105,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
}
// Player cannot invite someone already on a team
if (getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands()
&& getIslands().inTeam(getWorld(), invitedPlayerUUID)) {
user.sendMessage("commands.island.team.invite.errors.already-on-team");
return false;
}
@ -186,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(), island);
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(), 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;
@ -208,204 +189,4 @@ public class IslandTeamInviteCommand extends CompositeCommand {
return Optional.of(Util.tabLimit(options, lastArg));
}
/**
* 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(getPlugin().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) {
this.itc.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(user, this))
.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 = 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) {
getPlugin().logError("Icon in template is missing or unknown! " + template.toString());
}
if (template.title() == null) {
getPlugin().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 island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
return this.getBlankBackground();
}
if (page < 0) {
page = 0;
}
return getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
.filter(player -> this.searchName.isBlank() ? true
: player.getName().toLowerCase().contains(searchName.toLowerCase()))
.filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst()
.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(panel, user, clickType, clickSlot, player,
template.actions()))
.build();
}
private boolean clickHandler(Panel panel, User user, ClickType clickType, int clickSlot, Player player,
@NonNull List<ActionRecords> list) {
if (!list.stream().anyMatch(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 (this.canExecute(user, this.getLabel(), List.of(player.getName()))) {
getPlugin().log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in "
+ getWorld().getName());
this.execute(user, getLabel(), List.of(player.getName()));
} else {
getPlugin().log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in "
+ getWorld().getName());
}
} else if (clickType.equals(ClickType.RIGHT)) {
user.closeInventory();
if (this.itc.getCoopCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
getPlugin().log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in "
+ getWorld().getName());
this.itc.getCoopCommand().execute(user, getLabel(), List.of(player.getName()));
} else {
getPlugin().log(
"Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in "
+ getWorld().getName());
}
} else if (clickType.equals(ClickType.SHIFT_LEFT)) {
user.closeInventory();
if (this.itc.getTrustCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) {
getPlugin().log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in "
+ getWorld().getName());
this.itc.getTrustCommand().execute(user, getLabel(), List.of(player.getName()));
} else {
getPlugin().log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName()
+ " for island in "
+ 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();
}
/**
* @param searchName the searchName to set
*/
void setSearchName(String searchName) {
this.searchName = searchName;
}
}

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

@ -63,7 +63,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
user.sendMessage("commands.island.team.kick.cannot-kick");
return false;
}
if (!getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (!getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("general.errors.not-in-team");
return false;
}
@ -90,22 +90,25 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
}
}
protected void kick(User user, UUID targetUUID) {
protected boolean kick(User user, UUID targetUUID) {
if (targetUUID == null) {
return false;
}
User target = User.getInstance(targetUUID);
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be
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;
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, oldIsland);
@ -121,6 +124,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(oldIsland.getUniqueId(),
targetUUID.toString(), getSettings().getInviteCooldown() * 60));
}
return true;
}
@Override

View File

@ -15,6 +15,9 @@ 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;
@ -45,7 +48,7 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
showHelp(this, user);
return false;
}
// Check if the user has a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
@ -65,6 +68,11 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
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")) {
@ -100,7 +108,8 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
if (this.getLabel().equals("promote")) {
int nextRank = RanksManager.getInstance().getRankUpValue(currentRank);
// Stop short of owner
if (nextRank != RanksManager.OWNER_RANK && nextRank > currentRank) {
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());

View File

@ -42,7 +42,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
}
// Can use if in a team
Island is = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (is == null || !is.getMemberSet().contains(user.getUniqueId())) {
if (is == null || !is.inTeam(user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
}
@ -60,7 +60,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
user.sendMessage("commands.island.team.setowner.errors.cant-transfer-to-yourself");
return false;
}
if (!is.getMemberSet().contains(targetUUID)) {
if (!is.inTeam(targetUUID)) {
user.sendMessage("commands.island.team.setowner.errors.target-is-not-member");
return false;
}
@ -91,7 +91,6 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
IslandEvent.builder().island(island).involvedPlayer(user.getUniqueId()).admin(false)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK)
.build();
getIslands().save(island);
return true;
}

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;

View File

@ -74,7 +74,7 @@ public class IslandTeamUncoopCommand extends CompositeCommand {
user.sendMessage("commands.island.team.uncoop.cannot-uncoop-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.team.uncoop.cannot-uncoop-member");
return false;
}

View File

@ -74,7 +74,7 @@ public class IslandTeamUntrustCommand extends CompositeCommand {
user.sendMessage("commands.island.team.untrust.cannot-untrust-yourself");
return false;
}
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).getMemberSet().contains(targetUUID)) {
if (getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()).inTeam(targetUUID)) {
user.sendMessage("commands.island.team.untrust.cannot-untrust-member");
return false;
}

View File

@ -644,4 +644,13 @@ public interface WorldSettings extends ConfigObject {
default int getConcurrentIslands() {
return BentoBox.getInstance().getSettings().getIslandNumber();
}
/**
* Remove islands when players join a team and not allow players to have other islands if they are in a team.
* @return true or false
* @since 2.3.0
*/
default boolean isDisallowTeamMemberIslands() {
return true;
}
}

View File

@ -388,21 +388,24 @@ public class Flag implements Comparable<Flag> {
if (!user.isOp() && invisible) {
return null;
}
// Start the flag conversion
PanelItemBuilder pib = new PanelItemBuilder()
.icon(ItemParser.parse(user.getTranslationOrNothing(this.getIconReference()), new ItemStack(icon)))
.name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME, user.getTranslation(getNameReference())))
.name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME,
user.getTranslation(getNameReference())))
.clickHandler(clickHandler)
.invisible(invisible);
if (hasSubPanel()) {
pib.description(user.getTranslation("protection.panel.flag-item.menu-layout", TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
return pib.build();
}
return switch (getType()) {
case PROTECTION -> createProtectionFlag(plugin, user, island, pib).build();
case SETTING -> createSettingFlag(user, island, pib).build();
case WORLD_SETTING -> createWorldSettingFlag(user, world, pib).build();
};
}
private PanelItemBuilder createWorldSettingFlag(User user, World world, PanelItemBuilder pib) {
@ -429,19 +432,24 @@ public class Flag implements Comparable<Flag> {
private PanelItemBuilder createProtectionFlag(BentoBox plugin, User user, Island island, PanelItemBuilder pib) {
if (island != null) {
int y = island.getFlag(this);
// Protection flag
pib.description(user.getTranslation("protection.panel.flag-item.description-layout",
TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
if (score > RanksManager.BANNED_RANK && score < y) {
pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
} else if (score <= RanksManager.OWNER_RANK && score > island.getFlag(this)) {
} else if (score <= RanksManager.OWNER_RANK && score > y) {
pib.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + user.getTranslation(reference));
} else if (score == island.getFlag(this)) {
} else if (score == y) {
pib.description(user.getTranslation("protection.panel.flag-item.minimal-rank") + user.getTranslation(reference));
}
});
}
return pib;
}
@ -469,7 +477,7 @@ public class Flag implements Comparable<Flag> {
public Set<Flag> getSubflags() {
return subflags;
}
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.
@ -482,7 +490,7 @@ public class Flag implements Comparable<Flag> {
public boolean setTranslatedName(Locale locale, String name) {
return BentoBox.getInstance().getLocalesManager().setTranslation(locale, getNameReference(), name);
}
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.

View File

@ -4,6 +4,7 @@ import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -59,6 +60,11 @@ public class CycleClick implements PanelItem.ClickHandler {
@Override
public boolean onClick(Panel panel, User user2, ClickType click, int slot) {
if (panel.getWorld().isEmpty()) {
plugin.logError("Panel " + panel.getName()
+ " has no world associated with it. Please report this bug to the author.");
return true;
}
// This click listener is used with TabbedPanel and SettingsTabs only
TabbedPanel tp = (TabbedPanel)panel;
SettingsTab st = (SettingsTab)tp.getActiveTab();
@ -67,7 +73,7 @@ public class CycleClick implements PanelItem.ClickHandler {
this.user = user2;
changeOccurred = false;
// Permission prefix
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(user.getWorld()));
String prefix = plugin.getIWM().getPermissionPrefix(Util.getWorld(panel.getWorld().get()));
String reqPerm = prefix + "settings." + id;
String allPerms = prefix + "settings.*";
if (!user.hasPermission(reqPerm) && !user.hasPermission(allPerms)
@ -91,7 +97,7 @@ public class CycleClick implements PanelItem.ClickHandler {
rightClick(flag, currentRank);
} else if (click.equals(ClickType.SHIFT_LEFT) && user2.isOp()) {
leftShiftClick(flag);
leftShiftClick(flag, panel.getWorld().get());
}
});
} else {
@ -149,16 +155,16 @@ public class CycleClick implements PanelItem.ClickHandler {
}
private void leftShiftClick(Flag flag) {
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID())) {
plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID());
private void leftShiftClick(Flag flag, World world) {
if (!plugin.getIWM().getHiddenFlags(world).contains(flag.getID())) {
plugin.getIWM().getHiddenFlags(world).add(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
} else {
plugin.getIWM().getHiddenFlags(user.getWorld()).remove(flag.getID());
plugin.getIWM().getHiddenFlags(world).remove(flag.getID());
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1F, 1F);
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
plugin.getIWM().getAddon(world).ifPresent(GameModeAddon::saveWorldSettings);
}

View File

@ -48,7 +48,6 @@ public class PanelItem {
meta.addItemFlags(ItemFlag.HIDE_DESTROYS);
meta.addItemFlags(ItemFlag.HIDE_PLACED_ON);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
meta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS);
icon.setItemMeta(meta);
}
@ -89,7 +88,6 @@ public class PanelItem {
this.name = name;
if (meta != null) {
meta.setDisplayName(name);
meta.setLocalizedName(name); //Localized name cannot be overridden by the player using an anvils
icon.setItemMeta(meta);
}
}
@ -135,9 +133,9 @@ public class PanelItem {
}
if (meta != null) {
if (glow) {
meta.addEnchant(Enchantment.ARROW_DAMAGE, 0, glow);
meta.addEnchant(Enchantment.LURE, 0, glow);
} else {
meta.removeEnchant(Enchantment.ARROW_DAMAGE);
meta.removeEnchant(Enchantment.LURE);
}
icon.setItemMeta(meta);

View File

@ -74,6 +74,7 @@ public class TabbedPanel extends Panel implements PanelListener {
* @param page - the page of the tab to show (if multi paged)
*/
public void openPanel(int activeTab, int page) {
if (!tpb.getTabs().containsKey(activeTab)) {
// Request to open a non-existent tab
throw new InvalidParameterException("Attempt to open a non-existent tab in a tabbed panel. Missing tab #" + activeTab);
@ -88,21 +89,17 @@ public class TabbedPanel extends Panel implements PanelListener {
TreeMap<Integer, PanelItem> items = new TreeMap<>();
// Get the tab
Tab tab = tpb.getTabs().get(activeTab);
// Remove any tabs that have no items, if required
if (tpb.isHideIfEmpty()) {
tpb.getTabs().values().removeIf(t -> !t.equals(tab) && t.getPanelItems().stream().noneMatch(Objects::nonNull));
}
// Set up the tabbed header
setupHeader(tab, items);
// Show the active tab
if (tpb.getTabs().containsKey(activeTab)) {
List<PanelItem> panelItems = tab.getPanelItems();
// Adds the flag items
panelItems.stream().filter(Objects::nonNull).skip(page * ITEMS_PER_PAGE).limit(page * ITEMS_PER_PAGE + ITEMS_PER_PAGE).forEach(i -> items.put(items.lastKey() + 1, i));
// set up the footer
setupFooter(items);
// Add forward and backward icons
@ -182,6 +179,7 @@ public class TabbedPanel extends Panel implements PanelListener {
// Reset the closed flag
closed = false;
}
}
/**

View File

@ -14,6 +14,7 @@ import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
@ -33,6 +34,8 @@ import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.events.OfflineMessageEvent;
@ -62,19 +65,23 @@ public class User implements MetaDataAble {
private static final Map<Particle, Class<?>> VALIDATION_CHECK;
static {
Map<Particle, Class<?>> v = new EnumMap<>(Particle.class);
v.put(Particle.REDSTONE, Particle.DustOptions.class);
v.put(Particle.ITEM_CRACK, ItemStack.class);
v.put(Particle.BLOCK_CRACK, BlockData.class);
v.put(Particle.BLOCK_DUST, BlockData.class);
v.put(Enums.getIfPresent(Particle.class, "DUST")
.or(Enums.getIfPresent(Particle.class, "REDSTONE").or(Particle.FLAME)), Particle.DustOptions.class);
if (Enums.getIfPresent(Particle.class, "ITEM").isPresent()) {
// 1.20.6 Particles
v.put(Particle.ITEM, ItemStack.class);
v.put(Particle.ITEM_COBWEB, ItemStack.class);
v.put(Particle.BLOCK, BlockData.class);
v.put(Particle.DUST_PILLAR, BlockData.class);
v.put(Particle.ENTITY_EFFECT, Color.class);
}
v.put(Particle.FALLING_DUST, BlockData.class);
v.put(Particle.BLOCK_MARKER, BlockData.class);
v.put(Particle.DUST_COLOR_TRANSITION, DustTransition.class);
v.put(Particle.VIBRATION, Vibration.class);
v.put(Particle.SCULK_CHARGE, Float.class);
v.put(Particle.SHRIEK, Integer.class);
v.put(Particle.LEGACY_BLOCK_CRACK, BlockData.class);
v.put(Particle.LEGACY_BLOCK_DUST, BlockData.class);
v.put(Particle.LEGACY_FALLING_DUST, BlockData.class);
VALIDATION_CHECK = Collections.unmodifiableMap(v);
}
@ -711,8 +718,7 @@ public class User implements MetaDataAble {
* server's view distance.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
@ -730,7 +736,8 @@ public class User implements MetaDataAble {
// Check if this particle is beyond the viewing distance of the server
if (this.player != null && this.player.getLocation().toVector().distanceSquared(new Vector(x, y,
z)) < (Bukkit.getServer().getViewDistance() * 256 * Bukkit.getServer().getViewDistance())) {
if (particle.equals(Particle.REDSTONE)) {
if (particle.equals(Enums.getIfPresent(Particle.class, "DUST")
.or(Enums.getIfPresent(Particle.class, "REDSTONE").or(Particle.FLAME)))) {
player.spawnParticle(particle, x, y, z, 1, 0, 0, 0, 1, dustOptions);
} else if (dustOptions != null) {
player.spawnParticle(particle, x, y, z, 1, dustOptions);
@ -747,8 +754,7 @@ public class User implements MetaDataAble {
* server's view distance. Compatibility method for older usages.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
@ -762,8 +768,7 @@ public class User implements MetaDataAble {
* server's view distance.
*
* @param particle Particle to display.
* @param dustOptions Particle.DustOptions for the particle to display. Cannot
* be null when particle is {@link Particle#REDSTONE}.
* @param dustOptions Particle.DustOptions for the particle to display.
* @param x X coordinate of the particle to display.
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.

View File

@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -43,6 +44,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.hooks.MythicMobsHook;
/**
* The clipboard provides the holding spot for an active blueprint that is being
@ -67,6 +69,7 @@ public class BlueprintClipboard {
private final Map<Vector, BlueprintBlock> bpAttachable = new LinkedHashMap<>();
private final Map<Vector, BlueprintBlock> bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional<MythicMobsHook> mmh;
/**
* Create a clipboard for blueprint
@ -74,9 +77,16 @@ public class BlueprintClipboard {
*/
public BlueprintClipboard(@NonNull Blueprint blueprint) {
this.blueprint = blueprint;
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
public BlueprintClipboard() { }
public BlueprintClipboard() {
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
/**
* Copy the blocks between pos1 and pos2 into the clipboard for a user.
@ -285,6 +295,7 @@ public class BlueprintClipboard {
List<BlueprintEntity> bpEnts = new ArrayList<>();
for (LivingEntity entity: entities) {
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
bpe.setCustomName(entity.getCustomName());
if (entity instanceof Villager villager) {
@ -317,6 +328,10 @@ public class BlueprintClipboard {
if (entity instanceof Horse horse) {
bpe.setStyle(horse.getStyle());
}
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);
bpEnts.add(bpe);
}
return bpEnts;

View File

@ -36,6 +36,9 @@ import world.bentobox.bentobox.util.Util;
*/
public class BlueprintPaster {
/**
* This tracks the stages of pasting from loading the chunk, pasting blocks, attachments, entities and then finishing.
*/
enum PasteState {
CHUNK_LOAD,
CHUNK_LOADING,
@ -55,6 +58,7 @@ public class BlueprintPaster {
private final BentoBox plugin;
private final PasteHandler paster = Util.getPasteHandler();
private final PasteHandler fallback = new world.bentobox.bentobox.nms.fallback.PasteHandlerImpl();
private final World world;
// The minimum block position (x,y,z)
private Location pos1;
@ -115,6 +119,9 @@ public class BlueprintPaster {
Vector off = bp.getBedrock() != null ? bp.getBedrock() : new Vector(0,0,0);
// Calculate location for pasting
this.location = island.getProtectionCenter().toVector().subtract(off).toLocation(world);
// Ensure the y coordinate is within the world limits
int y = Math.min(world.getMaxHeight() - 1, Math.max(world.getMinHeight(), location.getBlockY()));
location.setY(y);
}
private record Bits(Map<Vector, BlueprintBlock> blocks,
@ -124,10 +131,20 @@ public class BlueprintPaster {
Iterator<Entry<Vector, BlueprintBlock>> it2,
Iterator<Entry<Vector, List<BlueprintEntity>>> it3,
int pasteSpeed) {}
/**
* The main pasting method
*/
public CompletableFuture<Boolean> paste() {
return this.paste(true);
}
/**
* Paste the clipboard
* @param useNMS if true, NMS pasting will be used, otherwise Bukkit API
* @return Future boolean where true is success
*/
public CompletableFuture<Boolean> paste(boolean useNMS) {
CompletableFuture<Boolean> result = new CompletableFuture<>();
// Iterators for the various maps to paste
final Map<Vector, BlueprintBlock> blocks = blueprint.getBlocks() == null ? Collections.emptyMap() : blueprint.getBlocks();
@ -145,12 +162,12 @@ public class BlueprintPaster {
Bits bits = new Bits(blocks, attached, entities,
blocks.entrySet().iterator(), attached.entrySet().iterator(), entities.entrySet().iterator(),
plugin.getSettings().getPasteSpeed());
pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> pasterTask(result, owner, bits), 0L, 1L);
pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> pasterTask(result, owner, bits, useNMS), 0L, 1L);
return result;
}
private void pasterTask(CompletableFuture<Boolean> result, Optional<User> owner, Bits bits) {
private void pasterTask(CompletableFuture<Boolean> result, Optional<User> owner, Bits bits, boolean useNMS) {
if (!currentTask.isDone()) return;
final int pasteSpeed = plugin.getSettings().getPasteSpeed();
@ -160,10 +177,10 @@ public class BlueprintPaster {
loadChunk();
}
else if (pasteState.equals(PasteState.BLOCKS) || pasteState.equals(PasteState.ATTACHMENTS)) {
pasteBlocks(bits, count, owner, pasteSpeed);
pasteBlocks(bits, count, owner, pasteSpeed, useNMS);
}
else if (pasteState.equals(PasteState.ENTITIES)) {
pasteEntities(bits, count, owner, pasteSpeed);
pasteEntities(bits, count, owner, pasteSpeed, useNMS);
}
else if (pasteState.equals(PasteState.DONE)) {
// All done. Cancel task
@ -185,7 +202,7 @@ public class BlueprintPaster {
result.complete(true);
}
private void pasteEntities(Bits bits, int count, Optional<User> owner, int pasteSpeed) {
private void pasteEntities(Bits bits, int count, Optional<User> owner, int pasteSpeed, boolean useNMS) {
if (bits.it3().hasNext()) {
Map<Location, List<BlueprintEntity>> entityMap = new HashMap<>();
// Paste entities
@ -203,7 +220,8 @@ public class BlueprintPaster {
count++;
}
if (!entityMap.isEmpty()) {
currentTask = paster.pasteEntities(island, world, entityMap);
currentTask = useNMS ? paster.pasteEntities(island, world, entityMap)
: fallback.pasteEntities(island, world, entityMap);
}
} else {
pasteState = PasteState.DONE;
@ -219,27 +237,10 @@ public class BlueprintPaster {
}
private void pasteBlocks(Bits bits, int count, Optional<User> owner, int pasteSpeed) {
private void pasteBlocks(Bits bits, int count, Optional<User> owner, int pasteSpeed, boolean useNMS) {
Iterator<Entry<Vector, BlueprintBlock>> it = pasteState.equals(PasteState.BLOCKS) ? bits.it : bits.it2;
if (it.hasNext()) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
// Paste blocks
while (count < pasteSpeed) {
if (!it.hasNext()) {
break;
}
Entry<Vector, BlueprintBlock> entry = it.next();
Location pasteTo = location.clone().add(entry.getKey());
// pos1 and pos2 update
updatePos(pasteTo);
BlueprintBlock block = entry.getValue();
blockMap.put(pasteTo, block);
count++;
}
if (!blockMap.isEmpty()) {
currentTask = paster.pasteBlocks(island, world, blockMap);
}
pasteBlocksNow(it, count, pasteSpeed, useNMS);
} else {
if (pasteState.equals(PasteState.BLOCKS)) {
// Blocks done
@ -256,6 +257,29 @@ public class BlueprintPaster {
}
private void pasteBlocksNow(Iterator<Entry<Vector, BlueprintBlock>> it, int count, int pasteSpeed, boolean useNMS) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
// Paste blocks
while (count < pasteSpeed) {
if (!it.hasNext()) {
break;
}
Entry<Vector, BlueprintBlock> entry = it.next();
Location pasteTo = location.clone().add(entry.getKey());
// pos1 and pos2 update
updatePos(pasteTo);
BlueprintBlock block = entry.getValue();
blockMap.put(pasteTo, block);
count++;
}
if (!blockMap.isEmpty()) {
currentTask = useNMS ? paster.pasteBlocks(island, world, blockMap)
: fallback.pasteBlocks(island, world, blockMap);
}
}
private void loadChunk() {
long timer = System.currentTimeMillis();
pasteState = PasteState.CHUNK_LOADING;

View File

@ -12,6 +12,7 @@ import org.bukkit.inventory.ItemStack;
import com.google.gson.annotations.Expose;
/**
* Represents a block to be pasted
* @author tastybento
* @since 1.5.0
*/
@ -19,8 +20,14 @@ public class BlueprintBlock {
@Expose
private String blockData;
/**
* Front of sign
*/
@Expose
private List<String> signLines;
/**
* Back of sign
*/
@Expose
private List<String> signLines2;
@Expose
@ -37,8 +44,14 @@ public class BlueprintBlock {
*/
@Expose
private List<Pattern> bannerPatterns;
/**
* Front of sign
*/
@Expose
private boolean glowingText;
/**
* Back of sign
*/
@Expose
private boolean glowingText2;

View File

@ -61,6 +61,11 @@ public class BlueprintBundle implements DataObject {
@Expose
private int slot = 0;
/**
* Number of times this bundle can be used by a single player. 0 = unlimited
*/
@Expose
private int times = 0;
/**
@ -188,4 +193,18 @@ public class BlueprintBundle implements DataObject {
this.slot = slot;
}
/**
* @return the times
*/
public int getTimes() {
return times;
}
/**
* @param times the times to set
*/
public void setTimes(int times) {
this.times = times;
}
}

View File

@ -24,6 +24,19 @@ import com.google.gson.annotations.Expose;
*/
public class BlueprintEntity {
public record MythicMobRecord(String type, String displayName, double level, float power, String stance) {
}
// GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries
@Expose
String MMtype;
@Expose
Double MMLevel;
@Expose
String MMStance;
@Expose
Float MMpower;
@Expose
private DyeColor color;
@Expose
@ -50,7 +63,6 @@ public class BlueprintEntity {
private Integer experience;
@Expose
private Villager.Type villagerType;
/**
* @since 1.8.0
@ -85,7 +97,6 @@ public class BlueprintEntity {
if (style != null && e instanceof Horse horse) {
horse.setStyle(style);
}
}
/**
@ -270,5 +281,27 @@ public class BlueprintEntity {
public void setDomestication(Integer domestication) {
this.domestication = domestication;
}
/**
* @return the mythicMobsRecord
*/
public MythicMobRecord getMythicMobsRecord() {
if (this.MMtype == null || this.MMLevel == null || this.MMpower == null || this.MMStance == null) {
return null;
}
return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance);
}
/**
* @param mmr the mythicMobsRecord to set
* @since 2.1.0
*/
public void setMythicMobsRecord(MythicMobRecord mmr) {
this.setCustomName(mmr.displayName());
this.MMtype = mmr.type();
this.MMLevel = mmr.level();
this.MMStance = mmr.stance();
this.MMpower = mmr.power();
}
}

View File

@ -28,7 +28,7 @@ public class BentoBoxAboutCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendRawMessage("About " + BentoBox.getInstance().getDescription().getName() + " v" + BentoBox.getInstance().getDescription().getVersion() + ":");
user.sendRawMessage("Copyright (c) 2017 - 2023 Tastybento, Poslovitch and the BentoBoxWorld contributors");
user.sendRawMessage("Copyright (c) 2017 - 2024 Tastybento, Poslovitch and the BentoBoxWorld contributors");
user.sendRawMessage("See https://www.eclipse.org/legal/epl-2.0/ for license information.");
return true;
}

View File

@ -43,6 +43,7 @@ public class BentoBoxVersionCommand extends CompositeCommand {
user.sendMessage("commands.bentobox.version.server",
TextVariables.NAME, serverSoftware.equals(ServerSoftware.UNKNOWN) ? user.getTranslation("general.invalid") + " (" + serverSoftware.getName() + ")" : serverSoftware.toString(),
TextVariables.VERSION, serverVersion != null ? serverVersion.toString() : user.getTranslation("general.invalid"));
user.sendRawMessage("(" + Bukkit.getVersion() + ")");
user.sendMessage("commands.bentobox.version.plugin-version", TextVariables.VERSION, getPlugin().getDescription().getVersion());
user.sendMessage("commands.bentobox.version.database", "[database]", getSettings().getDatabaseType().toString());
user.sendMessage("commands.bentobox.version.loaded-game-worlds");

View File

@ -13,6 +13,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
@ -41,6 +42,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
@ -56,7 +58,7 @@ import world.bentobox.bentobox.util.Util;
public class Island implements DataObject, MetaDataAble {
@Expose
private boolean primary;
private Set<UUID> primaries = new HashSet<>();
/**
* Set to true if this data object has been changed since being loaded from the
@ -243,7 +245,6 @@ public class Island implements DataObject, MetaDataAble {
range = BentoBox.getInstance().getIWM().getIslandDistance(world);
this.protectionRange = protectionRange;
this.maxEverProtectionRange = protectionRange;
this.setChanged();
}
/**
@ -290,6 +291,7 @@ public class Island implements DataObject, MetaDataAble {
this.updatedDate = island.getUpdatedDate();
this.world = island.getWorld();
this.bonusRanges.addAll(island.getBonusRanges());
this.primaries.addAll(island.getPrimaries());
this.setChanged();
}
@ -304,8 +306,10 @@ public class Island implements DataObject, MetaDataAble {
* @param playerUUID - the player's UUID
*/
public void addMember(@NonNull UUID playerUUID) {
setRank(playerUUID, RanksManager.MEMBER_RANK);
setChanged();
if (getRank(playerUUID) != RanksManager.MEMBER_RANK) {
setRank(playerUUID, RanksManager.MEMBER_RANK);
setChanged();
}
}
/**
@ -320,9 +324,12 @@ public class Island implements DataObject, MetaDataAble {
* @return {@code true}
*/
public boolean ban(@NonNull UUID issuer, @NonNull UUID target) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build());
setChanged();
if (getRank(target) != RanksManager.BANNED_RANK) {
setRank(target, RanksManager.BANNED_RANK);
log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString())
.build());
setChanged();
}
return true;
}
@ -1005,25 +1012,30 @@ public class Island implements DataObject, MetaDataAble {
* @param playerUUID - uuid of player
*/
public void removeMember(UUID playerUUID) {
members.remove(playerUUID);
setChanged();
if (members.remove(playerUUID) != null) {
setChanged();
}
}
/**
* @param center the center to set
*/
public void setCenter(@NonNull Location center) {
this.world = center.getWorld();
this.center = center;
setChanged();
if (this.center == null || !center.getWorld().equals(this.center.getWorld()) || !center.equals(this.center)) {
this.world = center.getWorld();
this.center = center;
setChanged();
}
}
/**
* @param createdDate - the createdDate to sets
*/
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
setChanged();
if (this.createdDate != createdDate) {
this.createdDate = createdDate;
setChanged();
}
}
/**
@ -1032,27 +1044,30 @@ public class Island implements DataObject, MetaDataAble {
*
* @param flag - flag
* @param value - Use RanksManager settings, e.g. RanksManager.MEMBER
* @return this island
*/
public void setFlag(Flag flag, int value) {
public Island setFlag(Flag flag, int value) {
setFlag(flag, value, true);
return this;
}
/**
* Set the Island Guard flag rank Also specify whether subflags are affected by
* this method call
* Set the Island Guard flag rank and set any subflags
*
* @param flag - flag
* @param value - Use RanksManager settings, e.g. RanksManager.MEMBER
* @param doSubflags - whether to set subflags
*/
public void setFlag(Flag flag, int value, boolean doSubflags) {
flags.put(flag.getID(), value);
if (flags.containsKey(flag.getID()) && flags.get(flag.getID()) != value) {
flags.put(flag.getID(), value);
setChanged();
}
// Subflag support
if (doSubflags && flag.hasSubflags()) {
// Ensure that a subflag isn't a subflag of itself or else we're in trouble!
flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true));
}
setChanged();
}
/**
@ -1066,9 +1081,10 @@ public class Island implements DataObject, MetaDataAble {
/**
* Resets the flags to their default as set in config.yml for this island. If
* flags are missing from the config, the default hard-coded value is used and
* set
* set.
* @return this island
*/
public void setFlagsDefaults() {
public Island setFlagsDefaults() {
BentoBox plugin = BentoBox.getInstance();
Map<String, Integer> result = new HashMap<>();
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION))
@ -1077,8 +1093,8 @@ public class Island implements DataObject, MetaDataAble {
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING))
.forEach(f -> result.put(f.getID(),
plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank())));
this.setFlags(result);
setChanged();
setFlags(result);
return this;
}
/**
@ -1097,8 +1113,10 @@ public class Island implements DataObject, MetaDataAble {
* @param name The display name to set.
*/
public void setName(String name) {
this.name = (name != null && !name.equals("")) ? name : null;
setChanged();
if (name == null || !name.equals(this.name)) {
this.name = (name != null && !name.equals("")) ? name : null;
setChanged();
}
}
/**
@ -1130,9 +1148,11 @@ public class Island implements DataObject, MetaDataAble {
* @param protectionRange the protectionRange to set
*/
public void setProtectionRange(int protectionRange) {
this.protectionRange = protectionRange;
this.updateMaxEverProtectionRange();
setChanged();
if (this.protectionRange != protectionRange) {
this.protectionRange = protectionRange;
this.updateMaxEverProtectionRange();
setChanged();
}
}
/**
@ -1164,8 +1184,10 @@ public class Island implements DataObject, MetaDataAble {
* @param purgeProtected - if the island is protected from the Purge
*/
public void setPurgeProtected(boolean purgeProtected) {
this.purgeProtected = purgeProtected;
setChanged();
if (this.purgeProtected != purgeProtected) {
this.purgeProtected = purgeProtected;
setChanged();
}
}
/**
@ -1179,8 +1201,10 @@ public class Island implements DataObject, MetaDataAble {
* @see #setProtectionRange(int)
*/
public void setRange(int range) {
this.range = range;
setChanged();
if (this.range != range) {
this.range = range;
setChanged();
}
}
/**
@ -1191,7 +1215,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setRank(User user, int rank) {
setRank(user.getUniqueId(), rank);
setChanged();
}
/**
@ -1199,17 +1222,36 @@ public class Island implements DataObject, MetaDataAble {
* the {@link world.bentobox.bentobox.api.events.island.IslandRankChangeEvent}.
*
* @param uuid UUID of the player
* @param rank rank value
* @param newRank rank value
* @since 1.1
*/
public void setRank(@Nullable UUID uuid, int rank) {
public void setRank(@Nullable UUID uuid, int newRank) {
// Early return if the UUID is null, to avoid unnecessary processing.
if (uuid == null) {
return; // Defensive code
return;
}
// Use an AtomicBoolean to track if the member's rank has been changed.
AtomicBoolean isRankChanged = new AtomicBoolean(false);
// Attempt to update the member's rank, if necessary.
members.compute(uuid, (key, existingRank) -> {
// If the member does not exist or their rank is different, update the rank.
if (existingRank == null || existingRank != newRank) {
isRankChanged.set(true);
return newRank; // Update the rank.
}
// No change needed; return the existing rank.
return existingRank;
});
// If the rank was changed, notify the change and log the update.
if (isRankChanged.get()) {
setChanged(); // Notify that a change has occurred.
}
members.put(uuid, rank);
setChanged();
}
/**
* @param ranks the ranks to set
*/
@ -1266,7 +1308,6 @@ public class Island implements DataObject, MetaDataAble {
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
setChanged();
}
/**
@ -1274,7 +1315,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setUpdatedDate(long updatedDate) {
this.updatedDate = updatedDate;
setChanged();
}
/**
@ -1347,8 +1387,13 @@ public class Island implements DataObject, MetaDataAble {
* @param l - location
*/
public void setSpawnPoint(Environment islandType, Location l) {
spawnPoint.put(islandType, l);
setChanged();
spawnPoint.compute(islandType, (key, value) -> {
if (value == null || !value.equals(l)) {
setChanged(); // Call setChanged only if the value is updated.
return l;
}
return value;
});
}
/**
@ -1368,8 +1413,9 @@ public class Island implements DataObject, MetaDataAble {
* @param rank rank value
*/
public void removeRank(Integer rank) {
members.values().removeIf(rank::equals);
setChanged();
if (members.values().removeIf(rank::equals)) {
setChanged();
}
}
/**
@ -1455,7 +1501,6 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setGameMode(String gameMode) {
this.gameMode = gameMode;
setChanged();
}
/**
@ -1518,8 +1563,9 @@ public class Island implements DataObject, MetaDataAble {
if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) {
return true;
}
cooldowns.remove(flag.getID());
setChanged();
if (cooldowns.remove(flag.getID()) != null) {
setChanged();
}
return false;
}
@ -1603,8 +1649,13 @@ public class Island implements DataObject, MetaDataAble {
public void setRankCommand(String command, int rank) {
if (this.commandRanks == null)
this.commandRanks = new HashMap<>();
this.commandRanks.put(command, rank);
setChanged();
commandRanks.compute(command, (key, value) -> {
if (value == null || !value.equals(rank)) {
setChanged(); // Call setChanged only if the value is updated.
return rank;
}
return value;
});
}
/**
@ -1624,8 +1675,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.6.0
*/
public void setReserved(boolean reserved) {
this.reserved = reserved;
setChanged();
if (this.reserved != reserved) {
this.reserved = reserved;
setChanged();
}
}
/**
@ -1658,17 +1711,19 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* Indicates the fields have been changed. Used to optimize saving on shutdown.
* Indicates the fields have been changed. Used to optimize saving on shutdown and notify other servers
*/
public void setChanged() {
this.setUpdatedDate(System.currentTimeMillis());
this.changed = true;
IslandsManager.updateIsland(this);
}
/**
* @param changed the changed to set
* Resets the changed if the island has been saved
*/
public void setChanged(boolean changed) {
this.changed = changed;
public void clearChanged() {
this.changed = false;
}
/**
@ -1692,6 +1747,9 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setProtectionCenter(Location location) throws IOException {
if (this.location.equals(location)) {
return; // nothing to do
}
if (!this.inIslandSpace(location)) {
throw new IOException("Location must be in island space");
}
@ -1741,6 +1799,9 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void addHome(String name, Location location) {
if (getHomes().containsKey(name) && getHomes().get(name).equals(location)) {
return; // nothing to do
}
if (location != null) {
Vector v = location.toVector();
if (!this.getBoundingBox().contains(v)) {
@ -1763,8 +1824,11 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public boolean removeHome(String name) {
setChanged();
return getHomes().remove(name.toLowerCase()) != null;
if (getHomes().remove(name.toLowerCase()) != null) {
setChanged();
return true;
}
return false;
}
/**
@ -1774,8 +1838,11 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.20.0
*/
public boolean removeHomes() {
setChanged();
return getHomes().keySet().removeIf(k -> !k.isEmpty());
if (getHomes().keySet().removeIf(k -> !k.isEmpty())) {
setChanged();
return true;
}
return false;
}
/**
@ -1814,8 +1881,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxHomes(@Nullable Integer maxHomes) {
this.maxHomes = maxHomes;
setChanged();
if (this.maxHomes != maxHomes) {
this.maxHomes = maxHomes;
setChanged();
}
}
/**
@ -1834,8 +1903,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxMembers(Map<Integer, Integer> maxMembers) {
this.maxMembers = maxMembers;
setChanged();
if (this.maxMembers != maxMembers) {
this.maxMembers = maxMembers;
setChanged();
}
}
/**
@ -1860,7 +1931,13 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.16.0
*/
public void setMaxMembers(int rank, Integer maxMembers) {
getMaxMembers().put(rank, maxMembers);
getMaxMembers().compute(rank, (key, value) -> {
if (value == null || !value.equals(maxMembers)) {
setChanged(); // Call setChanged only if the value is updated.
return maxMembers;
}
return value;
});
}
/**
@ -1923,8 +2000,9 @@ public class Island implements DataObject, MetaDataAble {
* @param id id to identify this bonus
*/
public void clearBonusRange(String id) {
this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id));
setChanged();
if (this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id))) {
setChanged();
}
}
/**
@ -1936,18 +2014,50 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* @param userID user UUID
* @return the primary
*/
public boolean isPrimary() {
return primary;
public boolean isPrimary(UUID userID) {
return getPrimaries().contains(userID);
}
/**
* @param primary the primary to set
* Set this island to be the primary for this user
* @param userID user UUID
*/
public void setPrimary(boolean primary) {
this.primary = primary;
setChanged();
public void setPrimary(UUID userID) {
if (getPrimaries().add(userID)) {
setChanged();
}
}
/**
* Remove the primary island
* @param userID user UUID
*/
public void removePrimary(UUID userID) {
if (getPrimaries().remove(userID)) {
setChanged();
}
}
/**
* Check if a player is in this island's team
* @param playerUUID player's UUID
* @return true if in team
* @since 2.3.0
*/
public boolean inTeam(UUID playerUUID) {
return this.getMemberSet().contains(playerUUID);
}
/**
* Check if this island has a team
* @return true if this island has a team
* @since 2.3.0
*/
public boolean hasTeam() {
return this.getMemberSet().size() > 1;
}
/*
@ -1967,4 +2077,41 @@ public class Island implements DataObject, MetaDataAble {
+ commandRanks + ", reserved=" + reserved + ", metaData=" + metaData + ", homes=" + homes
+ ", maxHomes=" + maxHomes + "]";
}
/**
* @return the primaries
*/
public Set<UUID> getPrimaries() {
if (primaries == null) {
primaries = new HashSet<>();
}
return primaries;
}
/**
* @param primaries the primaries to set
*/
public void setPrimaries(Set<UUID> primaries) {
this.primaries = primaries;
setChanged();
}
@Override
public int hashCode() {
return Objects.hash(uniqueId);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Island other = (Island) obj;
return Objects.equals(uniqueId, other.uniqueId);
}
}

View File

@ -6,13 +6,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.Expose;
@ -30,8 +27,6 @@ import world.bentobox.bentobox.util.Util;
*/
@Table(name = "Players")
public class Players implements DataObject, MetaDataAble {
@Expose
private Map<Location, Integer> homeLocations = new HashMap<>();
@Expose
private String uniqueId;
@Expose
@ -77,7 +72,6 @@ public class Players implements DataObject, MetaDataAble {
*/
public Players(BentoBox plugin, UUID uniqueId) {
this.uniqueId = uniqueId.toString();
homeLocations = new HashMap<>();
locale = "";
// Try to get player's name
this.playerName = Bukkit.getOfflinePlayer(uniqueId).getName();
@ -86,72 +80,6 @@ public class Players implements DataObject, MetaDataAble {
}
}
/**
* Gets the default home location.
* @param world - world to check
* @return Location - home location in world
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
@Nullable
public Location getHomeLocation(World world) {
return getHomeLocation(world, 1); // Default
}
/**
* Gets the home location by number for world
* @param world - includes world and any related nether or end worlds
* @param number - a number
* @return Location of this home or null if not available
* @deprecated Homes are stored in the island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
@Nullable
public Location getHomeLocation(World world, int number) {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations.entrySet().stream()
.filter(en -> Util.sameWorld(en.getKey().getWorld(), world) && en.getValue() == number)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
}
/**
* @param world - world
* @return Map of home locations
* @deprecated Homes are stored in the island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public Map<Location, Integer> getHomeLocations(World world) {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations.entrySet().stream().filter(e -> Util.sameWorld(e.getKey().getWorld(),world))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* @return the homeLocations
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public Map<Location, Integer> getHomeLocations() {
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
return homeLocations;
}
/**
* @param homeLocations the homeLocations to set
* @deprecated Homes are stored in the Island object now
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocations(Map<Location, Integer> homeLocations) {
this.homeLocations = homeLocations;
// Remove any lost worlds/locations
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null);
}
/**
* @param playerName the playerName to set
*/
@ -202,30 +130,6 @@ public class Players implements DataObject, MetaDataAble {
this.resets.put(world.getName(), resets);
}
/**
* Stores the home location of the player in a String format
*
* @param l a Bukkit location
* @deprecated Home locations are stored in islands
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocation(final Location l) {
setHomeLocation(l, 1);
}
/**
* Stores the numbered home location of the player. Numbering starts at 1.
* @param location - the location
* @param number - a number
* @deprecated Home locations are no longer stored for players. They are stored in islands.
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void setHomeLocation(Location location, int number) {
// Remove any home locations in the same world with the same number
homeLocations.entrySet().removeIf(e -> e.getKey() == null || (Util.sameWorld(location.getWorld(), e.getKey().getWorld()) && e.getValue().equals(number)));
homeLocations.put(location, number);
}
/**
* Set the uuid for this player object
* @param uuid - UUID
@ -234,16 +138,6 @@ public class Players implements DataObject, MetaDataAble {
uniqueId = uuid.toString();
}
/**
* Clears all home Locations in world
* @param world - world
* @deprecated Home locations are no longer stored for players. Use {@link world.bentobox.bentobox.managers.IslandsManager}
*/
@Deprecated(since="1.18.0", forRemoval=true)
public void clearHomeLocations(World world) {
homeLocations.keySet().removeIf(l -> l == null || l.getWorld() == null || Util.sameWorld(l.getWorld(), world));
}
/**
* @return the locale
*/
@ -350,24 +244,6 @@ public class Players implements DataObject, MetaDataAble {
}
}
/**
* Returns the display mode for the Flags in the Settings Panel.
* @return the display mode for the Flags in the Settings Panel.
* @since 1.6.0
*/
public Flag.Mode getFlagsDisplayMode() {
return flagsDisplayMode;
}
/**
* Sets the display mode for the Flags in the Settings Panel.
* @param flagsDisplayMode the display mode for the Flags in the Settings Panel.
* @since 1.6.0
*/
public void setFlagsDisplayMode(Flag.Mode flagsDisplayMode) {
this.flagsDisplayMode = flagsDisplayMode;
}
/**
* @return the metaData
* @since 1.15.5

View File

@ -12,7 +12,8 @@ import com.google.gson.annotations.Expose;
@Table(name = "Ranks")
public class Ranks implements DataObject {
public static final String ID = "BentoBox-Ranks";
@Expose
public String uniqueId = "BentoBox-Ranks";
public Ranks(Map<String, Integer> rankReference) {
super();
@ -24,12 +25,12 @@ public class Ranks implements DataObject {
@Override
public String getUniqueId() {
return ID;
return uniqueId;
}
@Override
public void setUniqueId(String uniqueId) {
// Nothing to do
this.uniqueId = uniqueId;
}
public Map<String, Integer> getRankReference() {

View File

@ -0,0 +1,112 @@
package world.bentobox.bentobox.database.objects;
import java.util.Objects;
import java.util.UUID;
import com.google.gson.annotations.Expose;
/**
* Data object for team invites
*/
@Table(name = "TeamInvites")
public class TeamInvite implements DataObject {
/**
* Type of invitation
*
*/
public enum Type {
COOP,
TEAM,
TRUST
}
@Expose
private Type type;
@Expose
private UUID inviter;
@Expose
private String islandID;
@Expose
private String uniqueId;
/**
* @param type - invitation type, e.g., coop, team, trust
* @param inviter - UUID of inviter
* @param invitee - UUID of invitee
* @param islandID - the unique ID of the island this invite is for
*/
public TeamInvite(Type type, UUID inviter, UUID invitee, String islandID) {
this.type = type;
this.uniqueId = invitee.toString();
this.inviter = inviter;
this.islandID = islandID;
}
@Override
public String getUniqueId() {
// Inviter
return this.uniqueId;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
/**
* @return the type
*/
public Type getType() {
return type;
}
/**
* @return the invitee
*/
public UUID getInvitee() {
return UUID.fromString(uniqueId);
}
/**
* @return the inviter
*/
public UUID getInviter() {
return inviter;
}
/**
* @return the islandID
*/
public String getIslandID() {
return islandID;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(inviter, uniqueId, 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 TeamInvite other)) {
return false;
}
return Objects.equals(inviter, other.inviter) && Objects.equals(uniqueId, other.getUniqueId())
&& type == other.type;
}
}

View File

@ -448,9 +448,13 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
Map<Object, Object> result = new HashMap<>();
for (Entry<Object, Object> object : value.entrySet()) {
// Serialize all key and values
String key = (String)serialize(object.getKey());
key = key.replace("\\.", ":dot:");
result.put(key, serialize(object.getValue()));
if (serialize(object.getKey()) instanceof String key) {
key = key.replace("\\.", ":dot:");
result.put(key, serialize(object.getValue()));
} else {
plugin.logWarning("Map key in config file could not be serialized, skipping. Entry is "
+ object.getKey() + ": " + object.getValue());
}
}
// Save the list in the config file
config.set(storageLocation, result);
@ -637,7 +641,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
.or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG));
}
// Backwards compatibility for upgrade to 1.20.4
if (name.equals("GRASS")) {
if (name.equals("GRASS") && Enums.getIfPresent(EntityType.class, "SHORT_GRASS").isPresent()) {
return Enums.getIfPresent(EntityType.class, "SHORT_GRASS");
}
value = Enum.valueOf(enumClass, name);

View File

@ -1,6 +1,11 @@
package world.bentobox.bentobox.hooks;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@ -8,6 +13,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.eclipse.jdt.annotation.Nullable;
import dev.lone.itemsadder.api.CustomBlock;
import world.bentobox.bentobox.BentoBox;
@ -22,6 +28,19 @@ import world.bentobox.bentobox.managers.RanksManager;
* Hook to enable itemsadder blocks to be deleted when islands are deleted.
* It also includes a flag to track explosion access
*/
/*
* add some methods under CustomBlock#Advanced class.
public static void deleteAllCustomBlocksInChunk(Chunk chunk)
@Nullable
public List<Location> getAllBlocksLocationsList(Chunk chunk)
@Nullable
public Map<String, Location> getAllBlocksLocations(Chunk chunk)
public void runActionOnBlocks(Chunk chunk, BiConsumer<String, Location> action)
*/
public class ItemsAdderHook extends Hook {
/**
@ -74,7 +93,26 @@ public class ItemsAdderHook extends Hook {
* @param location
*/
public void clearBlockInfo(Location location) {
CustomBlock.remove(location);
// TODO: find a more efficient way of doing this.
// CustomBlock.remove(location);
}
public static void deleteAllCustomBlocksInChunk(Chunk chunk) {
CustomBlock.Advanced.deleteAllCustomBlocksInChunk(chunk);
}
@Nullable
public List<Location> getAllBlocksLocationsList(Chunk chunk) {
return CustomBlock.Advanced.getAllBlocksLocationsList(chunk);
}
@Nullable
public Map<Location, String> getAllBlocksLocations(Chunk chunk) {
return CustomBlock.Advanced.getAllBlocksLocations(chunk);
}
public void runActionOnBlocks(Chunk chunk, BiConsumer<String, Location> action) {
CustomBlock.Advanced.runActionOnBlocks(chunk, action);
}
class BlockInteractListener extends FlagListener {

View File

@ -1,8 +1,10 @@
package world.bentobox.bentobox.hooks;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.bukkit.DyeColor;
import org.bukkit.Material;
@ -264,29 +266,48 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getPotionName(potionType, getUserLocale(user));
}
return generalPotionName(potionType);
}
private static String generalPotionName(PotionType potionType) {
return switch (potionType) {
case UNCRAFTABLE -> "Uncraftable Potion";
case WATER -> "Water Bottle";
case MUNDANE -> "Mundane Potion";
case THICK -> "Thick Potion";
case AWKWARD -> "Awkward Potion";
case NIGHT_VISION -> "Potion of Night Vision";
case INVISIBILITY -> "Potion of Invisibility";
case JUMP -> "Potion of Leaping";
case FIRE_RESISTANCE -> "Potion of Fire Resistance";
case SPEED -> "Potion of Swiftness";
case SLOWNESS -> "Potion of Slowness";
case WATER_BREATHING -> "Potion of Water Breathing";
case INSTANT_HEAL -> "Potion of Healing";
case INSTANT_DAMAGE -> "Potion of Harming";
case POISON -> "Potion of Poison";
case REGEN -> "Potion of Regeneration";
case STRENGTH -> "Potion of Strength";
case WEAKNESS -> "Potion of Weakness";
case LUCK -> "Potion of Luck";
case TURTLE_MASTER -> "Potion of the Turtle Master";
case SLOW_FALLING -> "Potion of Slow Falling";
default -> "Unknown Potion";
case LONG_FIRE_RESISTANCE -> "Potion of Long Fire Resistance";
case LONG_INVISIBILITY -> "Potion of Long Invisibility";
case LONG_NIGHT_VISION -> "Potion of Long Night Vision";
case LONG_POISON -> "Potion of Long Poison";
case LONG_REGENERATION -> "Potion of Long Regeneration";
case LONG_SLOWNESS -> "Potion of Long Slowness";
case LONG_SLOW_FALLING -> "Potion of Long Slow Falling";
case LONG_STRENGTH -> "Potion of Long Strength";
case LONG_SWIFTNESS -> "Potion of Long Swiftness";
case LONG_TURTLE_MASTER -> "Potion of Long Turtle Master";
case LONG_WATER_BREATHING -> "Potion of Long Water Breathing";
case LONG_WEAKNESS -> "Potion of Long Weakness";
case STRONG_HARMING -> "Potion of Strong Harming";
case STRONG_HEALING -> "Potion of Strong Healing";
case STRONG_LEAPING -> "Potion of Strong Leaping";
case STRONG_POISON -> "Potion of Strong Poison";
case STRONG_REGENERATION -> "Potion of Strong Regeneration";
case STRONG_SLOWNESS -> "Potion of Strong Slowness";
case STRONG_STRENGTH -> "Potion of Strong Strength";
case STRONG_SWIFTNESS -> "Potion of Swiftness";
case STRONG_TURTLE_MASTER -> "Potion of Strong Turtle Master";
default -> "Potion of " + Util.prettifyText(potionType.name());
};
}
@ -302,30 +323,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getSplashPotionName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Splash Uncraftable Potion";
case WATER -> "Splash Water Bottle";
case MUNDANE -> "Mundane Splash Potion";
case THICK -> "Thick Splash Potion";
case AWKWARD -> "Awkward Splash Potion";
case NIGHT_VISION -> "Splash Potion of Night Vision";
case INVISIBILITY -> "Splash Potion of Invisibility";
case JUMP -> "Splash Potion of Leaping";
case FIRE_RESISTANCE -> "Splash Potion of Fire Resistance";
case SPEED -> "Splash Potion of Swiftness";
case SLOWNESS -> "Splash Potion of Slowness";
case WATER_BREATHING -> "Splash Potion of Water Breathing";
case INSTANT_HEAL -> "Splash Potion of Healing";
case INSTANT_DAMAGE -> "Splash Potion of Harming";
case POISON -> "Splash Potion of Poison";
case REGEN -> "Splash Potion of Regeneration";
case STRENGTH -> "Splash Potion of Strength";
case WEAKNESS -> "Splash Potion of Weakness";
case LUCK -> "Splash Potion of Luck";
case TURTLE_MASTER -> "Splash Potion of the Turtle Master";
case SLOW_FALLING -> "Splash Potion of Slow Falling";
default -> "Unknown Splash Potion";
};
return "Splash" + generalPotionName(potionType);
}
/**
@ -339,30 +337,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getLingeringPotionName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Lingering Uncraftable Potion";
case WATER -> "Lingering Water Bottle";
case MUNDANE -> "Mundane Lingering Potion";
case THICK -> "Thick Lingering Potion";
case AWKWARD -> "Awkward Lingering Potion";
case NIGHT_VISION -> "Lingering Potion of Night Vision";
case INVISIBILITY -> "Lingering Potion of Invisibility";
case JUMP -> "Lingering Potion of Leaping";
case FIRE_RESISTANCE -> "Lingering Potion of Fire Resistance";
case SPEED -> "Lingering Potion of Swiftness";
case SLOWNESS -> "Lingering Potion of Slowness";
case WATER_BREATHING -> "Lingering Potion of Water Breathing";
case INSTANT_HEAL -> "Lingering Potion of Healing";
case INSTANT_DAMAGE -> "Lingering Potion of Harming";
case POISON -> "Lingering Potion of Poison";
case REGEN -> "Lingering Potion of Regeneration";
case STRENGTH -> "Lingering Potion of Strength";
case WEAKNESS -> "Lingering Potion of Weakness";
case LUCK -> "Lingering Potion of Luck";
case TURTLE_MASTER -> "Lingering Potion of the Turtle Master";
case SLOW_FALLING -> "Lingering Potion of Slow Falling";
default -> "Unknown Lingering Potion";
};
return "Lingering" + generalPotionName(potionType);
}
/**
@ -376,28 +351,7 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getTippedArrowName(potionType, getUserLocale(user));
}
return switch (potionType) {
case UNCRAFTABLE -> "Uncraftable Tipped Arrow";
case WATER -> "Arrow of Splashing";
case MUNDANE, THICK, AWKWARD -> "Tipped Arrow";
case NIGHT_VISION -> "Arrow of Night Vision";
case INVISIBILITY -> "Arrow of Invisibility";
case JUMP -> "Arrow of Leaping";
case FIRE_RESISTANCE -> "Arrow of Fire Resistance";
case SPEED -> "Arrow of Swiftness";
case SLOWNESS -> "Arrow of Slowness";
case WATER_BREATHING -> "Arrow of Water Breathing";
case INSTANT_HEAL -> "Arrow of Healing";
case INSTANT_DAMAGE -> "Arrow of Harming";
case POISON -> "Arrow of Poison";
case REGEN -> "Arrow of Regeneration";
case STRENGTH -> "Arrow of Strength";
case WEAKNESS -> "Arrow of Weakness";
case LUCK -> "Arrow of Luck";
case TURTLE_MASTER -> "Arrow of the Turtle Master";
case SLOW_FALLING -> "Arrow of Slow Falling";
default -> "Unknown Arrow";
};
return generalPotionName(potionType).replaceAll("Potion", "Arrow");
}
/**
@ -413,11 +367,12 @@ public class LangUtilsHook extends Hook {
if (hooked) {
return LanguageHelper.getPotionBaseEffectName(potionType, getUserLocale(user));
}
PotionEffectType effectType = potionType.getEffectType();
if (effectType == null) {
List<PotionEffect> effects = potionType.getPotionEffects();
if (effects.isEmpty()) {
return "No Effects";
}
return Util.prettifyText(effectType.getName());
return effects.stream().map(effect -> Util.prettifyText(effect.getType().getKey().getKey()))
.collect(Collectors.joining(", "));
}
/**
@ -430,7 +385,7 @@ public class LangUtilsHook extends Hook {
public static String getPotionEffectName(PotionEffectType effectType, User user) {
return hooked
? LanguageHelper.getPotionEffectName(effectType, getUserLocale(user))
: Util.prettifyText(effectType.getName());
: Util.prettifyText(effectType.getKey().getKey());
}
/**

View File

@ -0,0 +1,420 @@
package world.bentobox.bentobox.hooks;
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.DataStorageImpl;
import com.github.puregero.multilib.MultiLib;
import world.bentobox.bentobox.api.hooks.Hook;
/**
* Hook for Multipaper
*/
public class MultipaperHook extends Hook {
public MultipaperHook() {
super("multipaper", Material.PAPER);
}
@Override
public boolean hook() {
return MultiLib.isMultiPaper();
}
/**
* @return true if this is a Multipaper server
*/
@Override
public boolean isPluginAvailable() {
return MultiLib.isMultiPaper();
}
/**
* Always null because it is not a plugin
* @return null
*/
@Nullable
@Override
public Plugin getPlugin() {
return null;
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(World world, int cx, int cz) {
return MultiLib.isChunkExternal(world, cx, cz);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Location location) {
return MultiLib.isChunkExternal(location);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Entity entity) {
return MultiLib.isChunkExternal(entity);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Block block) {
return MultiLib.isChunkExternal(block);
}
/**
* Returns whether the chunk is running on an external server or not.
*
* @return True if the chunk is an external chunk, or false if the chunk
* is running on this server or if it's unloaded.
*/
public static boolean isChunkExternal(Chunk chunk) {
return MultiLib.isChunkExternal(chunk);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(World world, int cx, int cz) {
return MultiLib.isChunkLocal(world, cx, cz);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Location location) {
return MultiLib.isChunkLocal(location);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Entity entity) {
return MultiLib.isChunkLocal(entity);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Block block) {
return MultiLib.isChunkLocal(block);
}
/**
* Returns whether the chunk is running on this server or not.
*
* @return True if the chunk is a local chunk, or false if the chunk
* is running on an external server or if it's unloaded.
*/
public static boolean isChunkLocal(Chunk chunk) {
return MultiLib.isChunkLocal(chunk);
}
/**
* Returns whether the player is on an external server or not.
*
* @return True if the player is on an external server.
*/
public static boolean isExternalPlayer(Player player) {
return MultiLib.isExternalPlayer(player);
}
/**
* Returns whether the player is on this server or not.
*
* @return True if the player is on this server.
*/
public static boolean isLocalPlayer(Player player) {
return MultiLib.isLocalPlayer(player);
}
/**
* Get the bungeecord name of this server.
*
* @return the bungeecord name of this server
*/
@NonNull
public static String getLocalServerName() {
return MultiLib.getLocalServerName();
}
/**
* Get the bungeecord name of the server that this player is on.
*
* @return The bungeecord name of the server the player is on for external
* players, or null for local players.
*/
@Nullable
public static String getExternalServerName(Player player) {
return MultiLib.getExternalServerName(player);
}
/**
* Returns cross-server data that is stored under the specified key. Note
* that all plugins share the same set of keys. This data is
* non-persistent, it will be lost when the player disconnects.
*
* @param key The key the data is stored under.
* @return The data stored under the key, or null if the key isn't set.
*/
public static String getData(Player player, String key) {
return MultiLib.getData(player, key);
}
/**
* Store cross-server data under the specified key. Note that all plugins
* share the same set of keys. This data is non-persistent, it will be
* lost when the player disconnects.
*
* @param key The key to store the data under.
* @param value The data to store under the key.
*/
public static void setData(Player player, String key, String value) {
MultiLib.setData(player, key, value);
}
/**
* Returns cross-server data that is stored under the specified key. Note
* that all plugins share the same set of keys. This data is persistent,
* it will be saved even if the player disconnects. This persistent data is
* saved onto the player's .dat file.
*
* @param key The key the data is stored under.
* @return The data stored under the key, or null if the key isn't set.
*/
public static String getPersistentData(Player player, String key) {
return MultiLib.getPersistentData(player, key);
}
/**
* Store cross-server data under the specified key. Note that all plugins
* share the same set of keys. This data is persistent, it will be saved
* even if the player disconnects. This persistent data is saved onto the
* player's .dat file.
*
* @param key The key to store the data under.
* @param value The data to store under the key.
*/
public static void setPersistentData(Player player, String key, String value) {
MultiLib.setPersistentData(player, key, value);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callback A handler for any data received
*/
public static void on(Plugin plugin, String channel, Consumer<byte[]> callback) {
MultiLib.on(plugin, channel, callback);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callback A handler for any data received
*/
public static void onString(Plugin plugin, String channel, Consumer<String> callback) {
MultiLib.onString(plugin, channel, callback);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callbackWithReply A handler for any data received, and a method to reply to the server on a specified channel
*/
public static void on(Plugin plugin, String channel,
BiConsumer<byte[], BiConsumer<String, byte[]>> callbackWithReply) {
MultiLib.on(plugin, channel, callbackWithReply);
}
/**
* Listen to notifications sent by other servers.
*
* @param plugin The plugin listening to these notifications
* @param channel The notification channel to listen to
* @param callbackWithReply A handler for any data received, and a method to reply to the server on a specified channel
*/
public static void onString(Plugin plugin, String channel,
BiConsumer<String, BiConsumer<String, String>> callbackWithReply) {
MultiLib.onString(plugin, channel, callbackWithReply);
}
/**
* Notify all other servers.
*
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(String channel, byte[] data) {
MultiLib.notify(channel, data);
}
/**
* Notify all other servers.
*
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(String channel, String data) {
MultiLib.notify(channel, data);
}
/**
* Notify other servers with the specified chunk loaded
*
* @param chunk The chunk that's loaded
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(Chunk chunk, String channel, byte[] data) {
MultiLib.notify(chunk, channel, data);
}
/**
* Notify other servers with the specified chunk loaded
*
* @param chunk The chunk that's loaded
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notify(Chunk chunk, String channel, String data) {
MultiLib.notify(chunk, channel, data);
}
/**
* Notify the owning server of the specified chunk.
* This chunk must be loaded on this server.
* This will notify this server if this server is the owning server.
*
* @param chunk The loaded chunk with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Chunk chunk, String channel, byte[] data) {
MultiLib.notifyOwningServer(chunk, channel, data);
}
/**
* Notify the owning server of the specified chunk.
* This chunk must be loaded on this server.
* This will notify this server if this server is the owning server.
*
* @param chunk The loaded chunk with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Chunk chunk, String channel, String data) {
MultiLib.notifyOwningServer(chunk, channel, data);
}
/**
* Notify the owning server of the specified player.
* This will notify this server if this server is the owning server.
*
* @param player The player with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Player player, String channel, byte[] data) {
MultiLib.notifyOwningServer(player, channel, data);
}
/**
* Notify the owning server of the specified player.
* This will notify this server if this server is the owning server.
*
* @param player The player with an owning server
* @param channel The notification channel to notify on
* @param data The data to notify other servers with
*/
public static void notifyOwningServer(Player player, String channel, String data) {
MultiLib.notifyOwningServer(player, channel, data);
}
/**
* Says a message (or runs a command) on other servers excluding this one.
*
* @param message The chat message to say
*/
public static void chatOnOtherServers(Player player, String message) {
MultiLib.chatOnOtherServers(player, message);
}
/**
* Returns all online players across all server instances.
*
* @return a view of all online players
*/
public static Collection<? extends Player> getAllOnlinePlayers() {
return MultiLib.getAllOnlinePlayers();
}
/**
* Returns players logged into your single local server instance.
*
* @return a view of players online on your local instance
*/
public static Collection<? extends Player> getLocalOnlinePlayers() {
return MultiLib.getLocalOnlinePlayers();
}
/**
* Gets the multipaper key-value data storage. Accessing this data is
* asynchronous. This storage medium is hosted on the Master instance,
* or a yaml file when using Bukkit.
*
* @return the multipaper data storage
*/
public static DataStorageImpl getDataStorage() {
return MultiLib.getDataStorage();
}
}

View File

@ -0,0 +1,73 @@
package world.bentobox.bentobox.hooks;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import io.lumine.mythic.bukkit.BukkitAdapter;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.core.mobs.ActiveMob;
import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord;
/**
* Provides implementation and interfacing to interact with MythicMobs.
*
* @author tastybento
* @since 2.2.0
*/
public class MythicMobsHook extends Hook {
public MythicMobsHook() {
super("MythicMobs", Material.CREEPER_HEAD);
}
public boolean isMythicMob(Entity bukkitEntity) {
return MythicBukkit.inst().getMobManager().isMythicMob(bukkitEntity);
}
public MythicMobRecord getMythicMob(Entity bukkitEntity) {
ActiveMob mm = MythicBukkit.inst().getMobManager().getActiveMob(bukkitEntity.getUniqueId()).orElse(null);
if (mm != null) {
return new MythicMobRecord(mm.getMobType(), mm.getDisplayName(), mm.getLevel(),
mm.getPower(),
mm.getStance());
}
return null;
}
@Override
public boolean hook() {
return true; // The hook process shouldn't fail
}
@Override
public String getFailureCause() {
return null; // The hook process shouldn't fail
}
/**
* Spawn a MythicMob
* @param mmr MythicMobRecord
* @param spawnLocation location
* @return true if spawn is successful
*/
public boolean spawnMythicMob(MythicMobRecord mmr, Location spawnLocation) {
if (!this.isPluginAvailable()) {
return false;
}
return MythicBukkit.inst().getMobManager().getMythicMob(mmr.type()).map(mob -> {
// A delay is required before spawning, I assume because the blocks are pasted using NMS
Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {
// spawns mob
ActiveMob activeMob = mob.spawn(BukkitAdapter.adapt(spawnLocation), mmr.level());
activeMob.setDisplayName(mmr.displayName());
activeMob.setPower(mmr.power());
activeMob.setStance(mmr.stance());
}, 40L);
return true;
}).orElse(false);
}
}

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.listeners;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -61,7 +62,7 @@ public class JoinLeaveListener implements Listener {
// Make sure the player is loaded into the cache or create the player if they
// don't exist
players.addPlayer(playerUUID);
players.getPlayer(playerUUID);
// Reset island resets if required
plugin.getIWM().getOverWorlds().stream()
@ -74,11 +75,14 @@ public class JoinLeaveListener implements Listener {
// Set the player's name (it may have changed), but only if it isn't empty
if (!user.getName().isEmpty()) {
players.setPlayerName(user);
players.save(playerUUID);
} else {
plugin.logWarning("Player that just logged in has no name! " + playerUUID);
}
// Set the primary island to the player's location if this is their island
plugin.getIslands().getIslandAt(user.getLocation()).filter(i -> user.getUniqueId().equals(i.getOwner()))
.ifPresent(i -> plugin.getIslands().setPrimaryIsland(playerUUID, i));
// If mobs have to be removed when a player joins, then wipe all the mobs on his
// island.
if (plugin.getIslands().locationIsOnIsland(event.getPlayer(), user.getLocation())
@ -106,7 +110,7 @@ public class JoinLeaveListener implements Listener {
private void firstTime(User user) {
// Make sure the player is loaded into the cache or create the player if they
// don't exist
players.addPlayer(user.getUniqueId());
players.getPlayer(user.getUniqueId());
plugin.getIWM().getOverWorlds().stream().filter(w -> plugin.getIWM().isCreateIslandOnFirstLoginEnabled(w))
.forEach(w -> {
@ -177,13 +181,15 @@ public class JoinLeaveListener implements Listener {
user.getPlayer().getInventory().clear();
}
playerData.getPendingKicks().remove(world.getName());
players.save(user.getUniqueId());
Set<String> kicks = playerData.getPendingKicks();
kicks.remove(world.getName());
playerData.setPendingKicks(kicks);
}
}
private void updateIslandRange(User user) {
plugin.getIslands().getIslands().stream()
plugin.getIslands().getIslands(user.getUniqueId()).stream()
.filter(island -> island.getOwner() != null && island.getOwner().equals(user.getUniqueId()))
.forEach(island -> {
// Check if new owner has a different range permission than the island size
@ -214,12 +220,11 @@ public class JoinLeaveListener implements Listener {
// Remove any coops if all the island players have left
// Go through all the islands this player is a member of, check if all members
// have left, remove coops
plugin.getIslands().getIslands().stream()
plugin.getIslands().getIslands(event.getPlayer().getUniqueId()).stream()
.filter(island -> island.getMembers().containsKey(event.getPlayer().getUniqueId())).forEach(island -> {
// Are there any online players still for this island?
if (Bukkit.getOnlinePlayers().stream().filter(p -> !event.getPlayer().equals(p))
.noneMatch(p -> island.getMemberSet().contains(p.getUniqueId()))) {
.noneMatch(p -> island.inTeam(p.getUniqueId()))) {
// No, there are no more players online on this island
// Tell players they are being removed
island.getMembers().entrySet().stream().filter(e -> e.getValue() == RanksManager.COOP_RANK)
@ -232,7 +237,6 @@ public class JoinLeaveListener implements Listener {
});
// Remove any coop associations from the player logging out
plugin.getIslands().clearRank(RanksManager.COOP_RANK, event.getPlayer().getUniqueId());
players.save(event.getPlayer().getUniqueId());
User.removePlayer(event.getPlayer());
}
}

View File

@ -61,6 +61,7 @@ public class PanelListenerManager implements Listener {
// Refresh
l.refreshPanel();
});
} else {
// Wrong name - delete this panel
openPanels.remove(user.getUniqueId());

View File

@ -0,0 +1,59 @@
package world.bentobox.bentobox.listeners;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.util.Util;
/**
* Updates chunks in seed worlds if they have been generated in the main world
* @author tastybento
*/
public class SeedWorldMakerListener implements Listener {
private final BentoBox plugin;
/**
* Whether BentoBox is ready or not.
* This helps to avoid hanging out the server on startup as a lot of {@link ChunkLoadEvent} are called at this time.
* @since 1.1
*/
private boolean ready;
public SeedWorldMakerListener(BentoBox bentoBox) {
this.plugin = bentoBox;
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent e) {
ready = true;
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent e) {
if (!ready || !e.getChunk().isGenerated()) {
return;
}
World world = e.getWorld();
plugin.getIWM().getAddon(world).filter(GameModeAddon::isUsesNewChunkGeneration).ifPresent(gma -> {
World seed = Bukkit.getWorld(world.getName() + "/bentobox");
int x = e.getChunk().getX();
int z = e.getChunk().getZ();
if (seed != null && !seed.getChunkAt(x, z, false).isGenerated()) {
Util.getChunkAtAsync(seed, x, z, true);
}
});
}
}

View File

@ -5,6 +5,7 @@ import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler;
import world.bentobox.bentobox.api.user.User;
@ -18,6 +19,7 @@ import world.bentobox.bentobox.managers.RanksManager;
*/
public class CommandCycleClick implements ClickHandler {
protected static final String COMMAND_RANK_PREFIX = "COMMAND_RANK:";
private final BentoBox plugin = BentoBox.getInstance();
private final String command;
private final CommandRankClickListener commandRankClickListener;
@ -53,16 +55,33 @@ public class CommandCycleClick implements ClickHandler {
island.setRankCommand(command, RanksManager.getInstance().getRankDownValue(currentRank));
}
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F);
} else if (click.equals(ClickType.SHIFT_LEFT) && user.isOp()) {
leftShiftClick(user);
}
// Apply change to panel
panel.getInventory().setItem(slot, commandRankClickListener.getPanelItem(command, user, world).getItem());
// Save island
plugin.getIslands().save(island);
} else {
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F);
}
return true;
}
/**
* Adds or removes the command rank from visibility by non-Ops
* @param user - the Op requesting the change
*/
private void leftShiftClick(User user) {
String configSetting = COMMAND_RANK_PREFIX + command;
if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(configSetting)) {
plugin.getIWM().getHiddenFlags(user.getWorld()).add(configSetting);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
} else {
plugin.getIWM().getHiddenFlags(user.getWorld()).remove(configSetting);
user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1F, 1F);
}
// Save changes
plugin.getIWM().getAddon(user.getWorld()).ifPresent(GameModeAddon::saveWorldSettings);
}
}

View File

@ -77,7 +77,7 @@ public class CommandRankClickListener implements ClickHandler {
if (panel.getName().equals(panelName)) {
// This is a click on the panel
// Slot relates to the command
String c = getCommands(panel.getWorld().orElse(user.getWorld())).get(slot);
String c = getCommands(panel.getWorld().orElse(user.getWorld()), user).get(slot);
// Apply change to panel
panel.getInventory().setItem(slot, getPanelItem(c, user, panel.getWorld().orElse(user.getWorld())).getItem());
} else {
@ -94,7 +94,7 @@ public class CommandRankClickListener implements ClickHandler {
PanelBuilder pb = new PanelBuilder();
pb.user(user).name(panelName).world(world);
// Make panel items
getCommands(world).forEach(c -> pb.item(getPanelItem(c, user, world)));
getCommands(world, user).forEach(c -> pb.item(getPanelItem(c, user, world)));
pb.build();
}
@ -123,18 +123,19 @@ public class CommandRankClickListener implements ClickHandler {
pib.description(user.getTranslation("protection.panel.flag-item.minimal-rank") + user.getTranslation(reference));
}
});
pib.invisible(plugin.getIWM().getHiddenFlags(world).contains(CommandCycleClick.COMMAND_RANK_PREFIX + c));
return pib.build();
}
private List<String> getCommands(World world) {
List<String> result = new ArrayList<>();
plugin.getCommandsManager().getCommands().values().stream()
.filter(c -> c.getWorld() != null && c.getWorld().equals(world))
.forEach(c -> result.addAll(getCmdRecursively("/", c)));
if (result.size() > 49) {
plugin.logError("Number of rank setting commands is too big for GUI");
result.subList(49, result.size()).clear();
}
private List<String> getCommands(World world, User user) {
List<String> hiddenItems = plugin.getIWM().getHiddenFlags(world);
List<String> result = plugin.getCommandsManager().getCommands().values().stream()
.filter(c -> c.getWorld() != null && c.getWorld().equals(world)) // Only allow commands in this world
.filter(c -> c.testPermission(user.getSender())) // Only allow them to see commands they have permission to see
.flatMap(c -> getCmdRecursively("/", c).stream())
.filter(label -> user.isOp() || !hiddenItems.contains(CommandCycleClick.COMMAND_RANK_PREFIX + label))
.limit(49) // Silently limit to 49
.toList();
return result;
}

View File

@ -80,16 +80,20 @@ public class BreakBlocksListener extends FlagListener {
Player p = e.getPlayer();
Location l = e.getClickedBlock().getLocation();
Material m = e.getClickedBlock().getType();
// Check for berry picking
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && (e.getClickedBlock().getType() == Material.CAVE_VINES || e.getClickedBlock().getType() == Material.CAVE_VINES_PLANT)) {
if (!((CaveVinesPlant) e.getClickedBlock().getBlockData()).isBerries()) {
return;
// Right click handling
if (e.getAction() == Action.RIGHT_CLICK_BLOCK) {
Material clickedType = e.getClickedBlock().getType();
switch (clickedType) {
case CAVE_VINES, CAVE_VINES_PLANT -> {
if (((CaveVinesPlant) e.getClickedBlock().getBlockData()).isBerries()) {
this.checkIsland(e, p, l, Flags.HARVEST);
}
this.checkIsland(e, p, l, Flags.HARVEST);
return;
}
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getClickedBlock().getType() == Material.SWEET_BERRY_BUSH) {
this.checkIsland(e, p, l, Flags.HARVEST);
}
case SWEET_BERRY_BUSH -> this.checkIsland(e, p, l, Flags.HARVEST);
case ROOTED_DIRT -> this.checkIsland(e, p, l, Flags.BREAK_BLOCKS);
default -> { // Do nothing
}
}
return;
}
// Only handle hitting things

View File

@ -17,6 +17,7 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
@ -28,7 +29,6 @@ import world.bentobox.bentobox.lists.Flags;
*
*/
public class BreedingListener extends FlagListener {
/**
* A list of items that cause breeding if a player has them in their hand and they click an animal
* This list may need to be extended with future versions of Minecraft.
@ -41,7 +41,10 @@ public class BreedingListener extends FlagListener {
bi.put(EntityType.HORSE, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));
bi.put(EntityType.DONKEY, Arrays.asList(Material.GOLDEN_APPLE, Material.GOLDEN_CARROT));
bi.put(EntityType.COW, Collections.singletonList(Material.WHEAT));
bi.put(EntityType.MUSHROOM_COW, Collections.singletonList(Material.WHEAT));
Optional<EntityType> mc = Enums.getIfPresent(EntityType.class, "MUSHROOM_COW");
if (mc.isPresent()) {
bi.put(mc.get(), Collections.singletonList(Material.WHEAT));
}
bi.put(EntityType.SHEEP, Collections.singletonList(Material.WHEAT));
bi.put(EntityType.PIG, Arrays.asList(Material.CARROT, Material.POTATO, Material.BEETROOT));
bi.put(EntityType.CHICKEN, Arrays.asList(Material.WHEAT_SEEDS, Material.PUMPKIN_SEEDS, Material.MELON_SEEDS, Material.BEETROOT_SEEDS));

View File

@ -28,7 +28,6 @@ import org.bukkit.inventory.BeaconInventory;
import org.bukkit.inventory.BrewerInventory;
import org.bukkit.inventory.CartographyInventory;
import org.bukkit.inventory.ChiseledBookshelfInventory;
import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.DoubleChestInventory;
import org.bukkit.inventory.EnchantingInventory;
import org.bukkit.inventory.FurnaceInventory;
@ -185,9 +184,6 @@ public class InventoryListener extends FlagListener
} else if (e.getInventory() instanceof ChiseledBookshelfInventory) {
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BOOKSHELF);
return true;
} else if (e.getInventory() instanceof CraftingInventory) {
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CRAFTING);
return true;
} else if (e.getInventory() instanceof DoubleChestInventory) {
checkInvHolder(e.getInventory().getLocation(), e, player);
return true;

View File

@ -9,6 +9,7 @@ import org.bukkit.event.player.PlayerUnleashEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -42,7 +43,8 @@ public class LeashListener extends FlagListener {
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerLeashHitch(final HangingPlaceEvent e) {
if (e.getEntity().getType().equals(EntityType.LEASH_HITCH)) {
EntityType LEASH_HITCH = Util.findFirstMatchingEnum(EntityType.class, "LEASH_HITCH", "LEASH_KNOT");
if (e.getEntity().getType().equals(LEASH_HITCH)) {
checkIsland(e, e.getPlayer(), e.getEntity().getLocation(), Flags.LEASH);
}
}

View File

@ -15,7 +15,7 @@ import world.bentobox.bentobox.lists.Flags;
/**
* Listener for {@link Flags#CROP_TRAMPLE, Flags#PRESSURE_PLATE, Flags#TURTLE_EGGS, Flags#BUTTON}
* Listener for {@link Flags#CROP_TRAMPLE}, {@link Flags#PRESSURE_PLATE}, {@link Flags#TURTLE_EGGS}, {@link Flags#BUTTON}
* @author tastybento
*
*/

View File

@ -20,6 +20,7 @@ import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
/**
* Provides protection for placing blocks.
* @author tastybento
*/
public class PlaceBlocksListener extends FlagListener

View File

@ -4,6 +4,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerShearEntityEvent;
import io.papermc.paper.event.block.PlayerShearBlockEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
@ -20,4 +21,10 @@ public class ShearingListener extends FlagListener {
checkIsland(e, e.getPlayer(), e.getEntity().getLocation(), Flags.SHEARING);
}
// Block shearing - paper only
@EventHandler(priority = EventPriority.LOW)
public void onShearBlock(final PlayerShearBlockEvent e) {
checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.SHEARING);
}
}

View File

@ -17,6 +17,9 @@ import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
@ -25,13 +28,26 @@ import world.bentobox.bentobox.lists.Flags;
* @author tastybento
*/
public class TNTListener extends FlagListener {
/**
* Contains {@link EntityType}s that generates an explosion.
* @since 1.5.0
*/
private static final List<EntityType> TNT_TYPES = List.of(EntityType.PRIMED_TNT, EntityType.MINECART_TNT);
private static final List<EntityType> TNT_TYPES = List.of(
findFirstMatchingEnum(EntityType.class, "PRIMED_TNT", "TNT"),
findFirstMatchingEnum(EntityType.class, "MINECART_TNT", "TNT_MINECART"));
private static <T extends Enum<T>> T findFirstMatchingEnum(Class<T> enumClass, String... values) {
if (enumClass == null || values == null) {
return null;
}
for (String value : values) {
Optional<T> enumConstant = Enums.getIfPresent(enumClass, value.toUpperCase());
if (enumConstant.isPresent()) {
return enumConstant.get();
}
}
return null; // Return null if no match is found
}
/**
* Contains {@link Material}s that can be used to prime a TNT.
* @since 1.5.0

View File

@ -53,8 +53,9 @@ public class PVPListener extends FlagListener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityDamage(EntityDamageByEntityEvent e) {
if (e.getEntity() instanceof Player player && getPlugin().getIWM().inWorld(e.getEntity().getWorld())) {
// Allow self damage or NPC attack because Citizens handles its own PVP
if (e.getEntity().equals(e.getDamager()) || e.getEntity().hasMetadata("NPC")) {
// Allow self damage or NPC attack or attack by NPC because Citizens handles its own PVP
if (e.getEntity().equals(e.getDamager()) || e.getEntity().hasMetadata("NPC")
|| e.getDamager().hasMetadata("NPC")) {
return;
}
// Is PVP allowed here?
@ -224,9 +225,7 @@ public class PVPListener extends FlagListener {
// Only care about PVP Flags
if (Flags.PVP_OVERWORLD.equals(flag) || Flags.PVP_NETHER.equals(flag) || Flags.PVP_END.equals(flag)) {
String message = "protection.flags." + flag.getID() + "." + (e.isSetTo() ? "enabled" : "disabled");
// Send the message to visitors
e.getIsland().getVisitors().forEach(visitor -> User.getInstance(visitor).sendMessage(message));
// Send the message to players on the island
// Send the message to all players on the island
e.getIsland().getPlayersOnIsland().forEach(player -> User.getInstance(player).sendMessage(message));
}
}
@ -268,7 +267,7 @@ public class PVPListener extends FlagListener {
private void alertUser(@NonNull Player player, Flag flag) {
String message = "protection.flags." + flag.getID() + ".enabled";
User.getInstance(player).sendMessage(message);
User.getInstance(player).notify(message);
player.playSound(player.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER,2F, 1F);
}
}

View File

@ -1,5 +1,8 @@
package world.bentobox.bentobox.listeners.flags.worldsettings;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
@ -8,6 +11,7 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -68,4 +72,33 @@ public class CreeperListener extends FlagListener {
e.setCancelled(true);
}
}
/**
* Prevent creepers from igniting if they are not allowed to grief
* @param e - event
* @since 2.4.0
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent e)
{
Player player = e.getPlayer();
Location location = e.getRightClicked().getLocation();
if (!Flags.CREEPER_GRIEFING.isSetForWorld(location.getWorld()) &&
e.getRightClicked() instanceof Creeper &&
!this.getIslandsManager().locationIsOnIsland(player, location))
{
Material mainHand = player.getInventory().getItemInMainHand().getType();
if (Material.FIRE_CHARGE.equals(mainHand) ||
Material.FLINT_AND_STEEL.equals(mainHand))
{
// Creeper igniting
User user = User.getInstance(player);
user.notify("protection.protected", TextVariables.DESCRIPTION, user.getTranslation(Flags.CREEPER_GRIEFING.getHintReference()));
e.setCancelled(true);
}
}
}
}

View File

@ -107,7 +107,7 @@ public class EnterExitListener extends FlagListener {
// Leave messages are always specific to this world
String islandMessage = user.getTranslation(island.getWorld(), ISLAND_MESSAGE, TextVariables.NAME, getPlugin().getPlayers().getName(island.getOwner()));
// Send specific message if the player is member of this island
if (island.getMemberSet().contains(user.getUniqueId())) {
if (island.inTeam(user.getUniqueId())) {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-leaving-your-island", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
} else {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-leaving", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
@ -135,7 +135,7 @@ public class EnterExitListener extends FlagListener {
// Enter messages are always specific to this world
String islandMessage = user.getTranslation(island.getWorld(), ISLAND_MESSAGE, TextVariables.NAME, getPlugin().getPlayers().getName(island.getOwner()));
// Send specific message if the player is member of this island
if (island.getMemberSet().contains(user.getUniqueId())) {
if (island.inTeam(user.getUniqueId())) {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-entering-your-island", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);
} else {
user.notify(island.getWorld(), "protection.flags.ENTER_EXIT_MESSAGES.now-entering", TextVariables.NAME, (island.getName() != null) ? island.getName() : islandMessage);

View File

@ -55,7 +55,7 @@ public class IslandRespawnListener extends FlagListener {
*
* @param e - event
*/
@EventHandler(priority = EventPriority.HIGHEST)
@EventHandler(priority = EventPriority.NORMAL)
public void onPlayerRespawn(PlayerRespawnEvent e) {
final UUID worldUUID = respawn.remove(e.getPlayer().getUniqueId());
if (worldUUID == null) {
@ -67,11 +67,11 @@ public class IslandRespawnListener extends FlagListener {
return; // world no longer available
}
World w = Util.getWorld(world);
String ownerName = e.getPlayer().getName();
if (w != null) {
final Location respawnLocation = getIslands().getPrimaryIsland(world, e.getPlayer().getUniqueId())
.getSpawnPoint(world.getEnvironment());
if (respawnLocation != null) {
final Location respawnLocation = getIslands().getHomeLocation(world, e.getPlayer().getUniqueId());
if (respawnLocation != null && getIslands().isSafeLocation(respawnLocation)) {
e.setRespawnLocation(respawnLocation);
// Get the island owner name
Island island = BentoBox.getInstance().getIslands().getIsland(w, User.getInstance(e.getPlayer()));

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