Compare commits

...

111 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
217 changed files with 7036 additions and 4904 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

61
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.1.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,19 @@
<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>
@ -297,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>
@ -342,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>
@ -474,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>
@ -488,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

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

@ -116,7 +116,6 @@ public class AdminUnregisterCommand extends ConfirmableCommand {
targetIsland.getMembers().clear();
targetIsland.log(new LogEntry.Builder("UNREGISTER").data("player", targetUUID.toString())
.data("admin", user.getUniqueId().toString()).build());
getIslands().save(targetIsland);
user.sendMessage("commands.admin.unregister.unregistered-island", TextVariables.XYZ, Util.xyz(targetIsland.getCenter().toVector()),
TextVariables.NAME, getPlayers().getName(targetUUID));
}

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

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

@ -52,10 +52,7 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(0));
return false;
}
// Find the island the player is an owner of
Map<String, Island> islands = getIslandsXYZ(targetUUID);
if (islands.isEmpty()) {
@ -77,12 +74,17 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
// 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(island -> Util.xyz(island.getCenter().toVector()), island -> island));
.collect(Collectors.toMap(is -> Util.xyz(is.getCenter().toVector()), is -> is));
}
@Override

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

@ -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 the player's other islands
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;
}

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

@ -119,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,
@ -237,25 +240,7 @@ public class BlueprintPaster {
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 = useNMS ? paster.pasteBlocks(island, world, blockMap)
: fallback.pasteBlocks(island, world, blockMap);
}
pasteBlocksNow(it, count, pasteSpeed, useNMS);
} else {
if (pasteState.equals(PasteState.BLOCKS)) {
// Blocks done
@ -272,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

@ -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
@ -51,7 +64,6 @@ public class BlueprintEntity {
@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);
}
}
/**
@ -271,4 +282,26 @@ public class BlueprintEntity {
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

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

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

@ -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 {
/**
@ -78,6 +97,24 @@ public class ItemsAdderHook extends Hook {
// 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

@ -60,9 +60,6 @@ public class CommandCycleClick implements ClickHandler {
}
// 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);
}

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

@ -17,6 +17,7 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.RayTraceResult;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.FlagListener;
@ -68,12 +69,20 @@ public class ObsidianScoopingListener extends FlagListener {
return lookForLava(e);
}
/**
* @param e PlayerInteractEvent
* @return false if obsidian not scooped, true if scooped
*/
private boolean lookForLava(PlayerInteractEvent e) {
Player player = e.getPlayer();
ItemStack bucket = e.getItem();
// Get block player is looking at
Block b = e.getPlayer().rayTraceBlocks(5, FluidCollisionMode.ALWAYS).getHitBlock();
RayTraceResult rtBlocks = e.getPlayer().rayTraceBlocks(5, FluidCollisionMode.ALWAYS);
if (rtBlocks == null) {
return false;
}
Block b = rtBlocks.getHitBlock();
if (!b.getType().equals(Material.OBSIDIAN)) {
// This should not be needed but might catch some attempts
return false;

View File

@ -31,7 +31,7 @@ public class PetTeleportListener extends FlagListener {
// Get where the pet is going
e.setCancelled(getIslands().getProtectedIslandAt(e.getTo())
// Not home island
.map(i -> !i.getMemberSet().contains(t.getOwner().getUniqueId()))
.map(i -> !i.inTeam(t.getOwner().getUniqueId()))
// Not any island
.orElse(true));
}

View File

@ -49,7 +49,7 @@ public class VisitorsStartingRaidListener extends FlagListener
Optional<Island> island = this.getIslands().getProtectedIslandAt(event.getPlayer().getLocation());
if (island.isPresent() && !island.get().getMemberSet().contains(event.getPlayer().getUniqueId()))
if (island.isPresent() && !island.get().inTeam(event.getPlayer().getUniqueId()))
{
event.setCancelled(true);
this.report(User.getInstance(event.getPlayer()),

View File

@ -674,7 +674,7 @@ public final class Flags {
/**
* Crop Planting
* Controls who gets to plant crops on tilled soil.
* Listener is {@link PlaceBlockListener}
* Listener is {@link world.bentobox.bentobox.listeners.flags.protection.PlaceBlocksListener}
* @since 1.23.0
*/
public static final Flag CROP_PLANTING = new Flag.Builder("CROP_PLANTING", Material.PUMPKIN_SEEDS).mode(Flag.Mode.BASIC).type(Type.PROTECTION).build();

View File

@ -17,6 +17,9 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Common Game Mode Placeholders
*/
public enum GameModePlaceholder {
/* World-related */

View File

@ -214,8 +214,11 @@ public class BlueprintClipboardManager {
}
private void unzipFiles(final ZipInputStream zipInputStream, final Path unzipFilePath) throws IOException {
if (!unzipFilePath.toFile().getCanonicalPath().startsWith(blueprintFolder.getCanonicalPath())) {
throw new IOException("Entry is outside of the target directory");
// Prevent directory traversal attacks by normalizing the path
if (!unzipFilePath.startsWith(blueprintFolder.getCanonicalFile().toPath().normalize())) {
throw new IOException(
"Blueprint file is trying to write outside of the target directory! Blocked attempt to write to "
+ unzipFilePath.toString());
}
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(unzipFilePath.toFile().getCanonicalPath()))) {
byte[] bytesIn = new byte[1024];

View File

@ -40,6 +40,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.metadata.MetaDataValue;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintPaster;
@ -484,6 +485,8 @@ public class BlueprintsManager {
b2 ->
pasteEnd(addon, bb, island).thenAccept(message -> sendMessage(island)).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task))));
}
// Set the bundle name
island.putMetaData("bundle", new MetaDataValue(name));
return true;
}

View File

@ -18,7 +18,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
@ -37,6 +36,10 @@ import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.github.puregero.multilib.MultiLib;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
@ -44,9 +47,9 @@ import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.flags.Flag;
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.Database;
import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.lists.Flags;
@ -65,29 +68,18 @@ public class IslandsManager {
private final BentoBox plugin;
/**
* One island can be spawn, this is the one - otherwise, this value is null
*/
@NonNull
private final Map<@NonNull World, @Nullable Island> spawn;
private Map<World, Island> spawns = new HashMap<>();
private Map<World, Location> last = new HashMap<>();
@NonNull
private Database<Island> handler;
/**
* The last locations where an island were put. This is not stored persistently
* and resets when the server starts
*/
private final Map<World, Location> last;
private static Database<Island> handler;
/**
* Island Cache
*/
@NonNull
private IslandCache islandCache;
// Quarantined islands
@NonNull
private final Map<UUID, List<Island>> quarantineCache;
// Deleted islands
@NonNull
private final List<String> deletedIslands;
@ -105,24 +97,57 @@ public class IslandsManager {
this.plugin = plugin;
// Set up the database handler to store and retrieve Island classes
handler = new Database<>(plugin, Island.class);
islandCache = new IslandCache();
quarantineCache = new HashMap<>();
spawn = new HashMap<>();
last = new HashMap<>();
islandCache = new IslandCache(handler);
// This list should always be empty unless database deletion failed
// In that case a purge utility may be required in the future
deletedIslands = new ArrayList<>();
// Mid-teleport players going home
goingHome = new HashSet<>();
// Set handler in Island
// Listen for Island Updates
MultiLib.onString(plugin, "bentobox-updateIsland", id -> {
Island island = handler.loadObject(id);
if (island != null) {
islandCache.updateIsland(island);
}
});
// Delete island blocks
MultiLib.onString(plugin, "bentobox-deleteIsland", id -> {
IslandDeletion idd = getGson().fromJson(id, IslandDeletion.class);
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(idd);
});
// List for new islands
MultiLib.onString(plugin, "bentobox-newIsland", id -> {
Island island = handler.loadObject(id);
if (island != null) {
islandCache.addIsland(island);
}
});
// Set or clear spawn
MultiLib.onString(plugin, "bentobox-setspawn", sp -> {
String[] split = sp.split(",");
if (split.length == 1) {
World world = Bukkit.getWorld(split[0]);
this.clearSpawn(world);
} else if (split.length == 2) {
World world = Bukkit.getWorld(split[0]);
if (world != null) {
getIslandById(split[1]).ifPresent(i -> this.setSpawn(i));
}
}
});
}
/**
* Used only for testing. Sets the database to a mock database.
*
* @param handler - handler
* @param h - handler
*/
public void setHandler(@NonNull Database<Island> handler) {
this.handler = handler;
public void setHandler(@NonNull Database<Island> h) {
handler = h;
}
/**
@ -227,14 +252,13 @@ public class IslandsManager {
.orElse("");
island.setGameMode(gmName);
island.setUniqueId(gmName + island.getUniqueId());
while (handler.objectExists(island.getUniqueId())) {
// This should never happen, so although this is a potential infinite loop I'm
// going to leave it here because
// it will be bad if this does occur and the server should crash.
plugin.logWarning("Duplicate island UUID occurred");
island.setUniqueId(gmName + UUID.randomUUID());
}
if (islandCache.addIsland(island)) {
// Save to database and notify other servers
handler.saveObjectAsync(island).thenAccept(b -> {
if (b.equals(Boolean.TRUE)) {
MultiLib.notify("bentobox-newIsland", island.getUniqueId());
}
});
return island;
}
return null;
@ -257,27 +281,40 @@ public class IslandsManager {
// Set the owner of the island to no one.
island.setOwner(null);
island.setFlag(Flags.LOCK, RanksManager.VISITOR_RANK);
island.setDeleted(true);
if (removeBlocks) {
// Remove island from the cache
islandCache.deleteIslandFromCache(island);
// Log the deletion (it shouldn't matter but may be useful)
island.log(new LogEntry.Builder("DELETED").build());
// Set the delete flag which will prevent it from being loaded even if database
// deletion fails
island.setDeleted(true);
// Save the island
handler.saveObjectAsync(island);
// Delete the island
handler.deleteObject(island);
// Remove players from island
removePlayersFromIsland(island);
if (!plugin.getSettings().isKeepPreviousIslandOnReset()) {
// Remove blocks from world
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(new IslandDeletion(island));
IslandDeletion id = new IslandDeletion(island);
plugin.getIslandDeletionManager().getIslandChunkDeletionManager().add(id);
// Tell other servers
MultiLib.notify("bentobox-deleteIsland", getGson().toJson(id));
}
// Delete the island from the database
handler.deleteObject(island);
}
}
private Gson getGson() {
// Build the Gson
// excludeFieldsWithoutExposeAnnotation - this means that every field to be stored should use @Expose
// enableComplexMapKeySerialization - forces GSON to use TypeAdapters even for Map keys
GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
.enableComplexMapKeySerialization().setPrettyPrinting();
// Register adapter factory
builder.registerTypeAdapterFactory(new BentoboxTypeAdapterFactory(plugin));
// Allow characters like < or > without escaping them
builder.disableHtmlEscaping();
return builder.create();
}
/**
* Get the number of islands made on this server. Used by stats.
*
@ -321,7 +358,7 @@ public class IslandsManager {
* @return List of islands or empty list if none found for user
*/
@NonNull
public Set<Island> getIslands(@NonNull World world, @NonNull User user) {
public List<Island> getIslands(@NonNull World world, @NonNull User user) {
return getIslands(world, user.getUniqueId());
}
@ -333,15 +370,26 @@ public class IslandsManager {
* @return List of islands or empty list if none found for user
*/
@NonNull
public Set<Island> getIslands(@NonNull World world, UUID uniqueId) {
public List<Island> getIslands(@NonNull World world, UUID uniqueId) {
return islandCache.getIslands(world, uniqueId);
}
/**
* Gets all the islands for this player in any world where this player has any presence
*
* @param uniqueId user's UUID
* @return List of islands or empty list if none found for user
*/
@NonNull
public List<Island> getIslands(UUID uniqueId) {
return islandCache.getIslands(uniqueId);
}
/**
* Gets all the islands for this player in this world that this player owns.
*
* @param world world to check
* @param uniqueId user's UUID
* @param user user
* @return List of islands or empty list if none found for user
* @since 2.1.0
*/
@ -378,7 +426,7 @@ public class IslandsManager {
*/
@Nullable
public Island getIsland(@NonNull World world, @NonNull UUID uuid) {
return islandCache.get(world, uuid);
return islandCache.getIsland(world, uuid);
}
/**
@ -403,7 +451,7 @@ public class IslandsManager {
*/
@NonNull
public Collection<Island> getIslands() {
return islandCache.getIslands();
return handler.loadObjects().stream().toList();
}
/**
@ -417,7 +465,7 @@ public class IslandsManager {
*/
@NonNull
public Collection<Island> getIslands(@NonNull World world) {
return islandCache.getIslands(world);
return handler.loadObjects().stream().filter(i -> world.equals(i.getWorld())).toList();
}
/**
@ -462,7 +510,7 @@ public class IslandsManager {
* Get the last location where an island was created
*
* @param world - world
* @return location
* @return location or null if none found
*/
public Location getLast(@NonNull World world) {
return last.get(world);
@ -486,7 +534,7 @@ public class IslandsManager {
if (island.getOwner() == null) {
// No owner, no rank settings
island.setMaxMembers(null);
this.save(island);
updateIsland(island);
return 0;
}
// Island max is either the world default or specified amount for this island
@ -507,8 +555,11 @@ public class IslandsManager {
islandMax = owner.getPermissionValue(plugin.getIWM().getPermissionPrefix(island.getWorld()) + perm,
islandMax);
}
island.setMaxMembers(rank, islandMax == worldDefault ? null : islandMax);
this.save(island);
Integer change = islandMax == worldDefault ? null : islandMax;
if (island.getMaxMembers().get(rank) != change) {
island.setMaxMembers(rank, change);
updateIsland(island);
}
return islandMax;
}
@ -546,13 +597,16 @@ public class IslandsManager {
}
// If the island maxHomes is just the same as the world default, then set to
// null
island.setMaxHomes(islandMax == plugin.getIWM().getMaxHomes(island.getWorld()) ? null : islandMax);
this.save(island);
Integer change = islandMax == plugin.getIWM().getMaxHomes(island.getWorld()) ? null : islandMax;
if (island.getMaxHomes() != change) {
island.setMaxHomes(change);
updateIsland(island);
}
return islandMax;
}
/**
* Set the maximum numbber of homes allowed on this island
* Set the maximum number of homes allowed on this island
*
* @param island - island
* @param maxHomes - max number of homes allowed, or null if the world default
@ -735,9 +789,9 @@ public class IslandsManager {
* @since 1.16.0
*/
public boolean setHomeLocation(@Nullable Island island, Location location, String name) {
if (island != null) {
if (island != null && (island.getHome(name) == null || !island.getHome(name).equals(location))) {
island.addHome(name, location);
this.save(island);
updateIsland(island);
return true;
}
return false;
@ -890,7 +944,7 @@ public class IslandsManager {
*/
@NonNull
public Optional<Island> getSpawn(@NonNull World world) {
return Optional.ofNullable(spawn.get(world));
return Optional.ofNullable(spawns.get(world));
}
/**
@ -901,7 +955,7 @@ public class IslandsManager {
*/
@Nullable
public Location getSpawnPoint(@NonNull World world) {
return spawn.containsKey(world) ? spawn.get(world).getSpawnPoint(world.getEnvironment()) : null;
return getSpawn(world).map(i -> i.getSpawnPoint(world.getEnvironment())).orElse(null);
}
/**
@ -1132,7 +1186,7 @@ public class IslandsManager {
* @return true if they are, false if they are not, or spawn does not exist
*/
public boolean isAtSpawn(Location playerLoc) {
return spawn.containsKey(playerLoc.getWorld()) && spawn.get(playerLoc.getWorld()).onIsland(playerLoc);
return getSpawn(playerLoc.getWorld()).map(i -> i.onIsland(playerLoc)).orElse(false);
}
/**
@ -1144,19 +1198,14 @@ public class IslandsManager {
* @param spawn the Island to set as spawn. Must not be null.
*/
public void setSpawn(@NonNull Island spawn) {
// Checking if there is already a spawn set for this world
if (this.spawn.containsKey(spawn.getWorld()) && this.spawn.get(spawn.getWorld()) != null) {
Island oldSpawn = this.spawn.get(spawn.getWorld());
if (oldSpawn.equals(spawn)) {
return; // The spawn is already the current spawn - no need to update anything.
} else {
oldSpawn.setSpawn(false);
}
if (spawn.getWorld() != null) {
spawns.put(Util.getWorld(spawn.getWorld()), spawn);
// Tell other servers
MultiLib.notify("bentobox-setspawn", spawn.getWorld().getUID().toString() + "," + spawn.getUniqueId());
}
this.spawn.put(spawn.getWorld(), spawn);
spawn.setSpawn(true);
}
/**
* Clears the spawn island for this world
*
@ -1164,11 +1213,9 @@ public class IslandsManager {
* @since 1.8.0
*/
public void clearSpawn(World world) {
Island spawnIsland = spawn.get(Util.getWorld(world));
if (spawnIsland != null) {
spawnIsland.setSpawn(false);
}
this.spawn.remove(world);
spawns.remove(world);
// Tell other servers
MultiLib.notify("bentobox-setspawn", world.getUID().toString());
}
/**
@ -1192,7 +1239,6 @@ public class IslandsManager {
*/
public void load() throws IOException {
islandCache.clear();
quarantineCache.clear();
List<Island> toQuarantine = new ArrayList<>();
int owned = 0;
int unowned = 0;
@ -1206,9 +1252,6 @@ public class IslandsManager {
if (island.isDeleted()) {
// These will be deleted later
deletedIslands.add(island.getUniqueId());
} else if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) {
// Add to quarantine cache
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
} // Check island distance and if incorrect stop BentoBox
else if (island.getWorld() != null && plugin.getIWM().inWorld(island.getWorld())
&& island.getRange() != plugin.getIWM().getIslandDistance(island.getWorld())) {
@ -1219,18 +1262,9 @@ public class IslandsManager {
} else {
// Fix island center if it is off
fixIslandCenter(island);
if (!islandCache.addIsland(island)) {
// Quarantine the offending island
toQuarantine.add(island);
// Add to quarantine cache
island.setDoNotLoad(true);
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
if (island.isUnowned()) {
unowned++;
} else {
owned++;
}
} else if (island.isSpawn()) {
islandCache.addIsland(island);
if (island.isSpawn()) {
// Success, set spawn if this is the spawn island.
this.setSpawn(island);
} else {
@ -1319,7 +1353,7 @@ public class IslandsManager {
return false;
}
// Get the player's island
return getIslandAt(loc).filter(i -> i.onIsland(loc)).map(i -> i.getMemberSet().contains(player.getUniqueId()))
return getIslandAt(loc).filter(i -> i.onIsland(loc)).map(i -> i.inTeam(player.getUniqueId()))
.orElse(false);
}
@ -1389,20 +1423,15 @@ public class IslandsManager {
.filter(p -> p.getGameMode().equals(plugin.getIWM().getDefaultGameMode(island.getWorld())))
.filter(p -> island.onIsland(p.getLocation())).forEach(p -> {
// Teleport island players to their island home
if (!island.getMemberSet().contains(p.getUniqueId())
if (!island.inTeam(p.getUniqueId())
&& (hasIsland(w, p.getUniqueId()) || inTeam(w, p.getUniqueId()))) {
homeTeleportAsync(w, p);
} else {
// Move player to spawn
if (spawn.containsKey(w)) {
// go to island spawn
Location sp = spawn.get(w).getSpawnPoint(w.getEnvironment());
if (sp != null) {
PaperLib.teleportAsync(p, sp);
} else {
plugin.logWarning("Spawn exists but its location is null!");
}
}
getSpawn(w).map(i -> i.getSpawnPoint(w.getEnvironment())).filter(Objects::nonNull)
.ifPresentOrElse(sp -> PaperLib.teleportAsync(p, sp),
() -> plugin.logWarning("Spawn exists but its location is null!"));
}
});
}
@ -1426,7 +1455,7 @@ public class IslandsManager {
*/
public void saveAll(boolean schedule) {
if (!schedule) {
for (Island island : islandCache.getIslands()) {
for (Island island : islandCache.getCachedIslands()) {
if (island.isChanged()) {
try {
handler.saveObjectAsync(island);
@ -1439,7 +1468,7 @@ public class IslandsManager {
}
isSaveTaskRunning = true;
Queue<Island> queue = new LinkedList<>(islandCache.getIslands());
Queue<Island> queue = new LinkedList<>(islandCache.getCachedIslands());
new BukkitRunnable() {
@Override
public void run() {
@ -1473,9 +1502,13 @@ public class IslandsManager {
teamIsland.addMember(playerUUID);
islandCache.addPlayer(playerUUID, teamIsland);
// Save the island
handler.saveObjectAsync(teamIsland);
updateIsland(teamIsland);
}
/**
* Set the last island location
* @param last location
*/
public void setLast(Location last) {
this.last.put(last.getWorld(), last);
}
@ -1493,8 +1526,9 @@ public class IslandsManager {
}
/**
* Checks if a player is in a team in this world. Note that the player may have
* Checks if a player is in any team in this world. Note that the player may have
* multiple islands in the world, any one of which may have a team.
* Consider checking the island itself {@link Island#inTeam(UUID)}
*
* @param world - world
* @param playerUUID - player's UUID
@ -1502,7 +1536,7 @@ public class IslandsManager {
*/
public boolean inTeam(World world, @NonNull UUID playerUUID) {
return this.islandCache.getIslands(world, playerUUID).stream()
.anyMatch(island -> island.getMemberSet().size() > 1 && island.getMemberSet().contains(playerUUID));
.anyMatch(island -> island.getMemberSet().size() > 1 && island.inTeam(playerUUID));
}
/**
@ -1520,17 +1554,22 @@ public class IslandsManager {
/**
* Sets this target as the owner for this island
*
* @param user previous owner
* @param user user making the change
* @param targetUUID new owner
* @param island island to register
* @param rank rank to which to set old owner.
*/
public void setOwner(User user, UUID targetUUID, Island island, int rank) {
islandCache.setOwner(island, targetUUID);
// Set old owner as sub-owner on island.
if (rank > RanksManager.VISITOR_RANK) {
island.setRank(user, rank);
// Demote the old owner
if (rank >= RanksManager.OWNER_RANK) {
plugin.logWarning("Setowner: previous owner's rank cannot be higher than SubOwner");
rank = RanksManager.SUB_OWNER_RANK;
}
if (rank > RanksManager.VISITOR_RANK && island.getOwner() != null) {
island.setRank(island.getOwner(), rank);
}
// Make the new owner
islandCache.setOwner(island, targetUUID);
user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]",
plugin.getPlayers().getName(targetUUID));
@ -1602,12 +1641,16 @@ public class IslandsManager {
}
/**
* Save the island to the database
* Update island data in database
*
* @param island - island
*/
public void save(Island island) {
handler.saveObjectAsync(island);
public static void updateIsland(Island island) {
if (handler.objectExists(island.getUniqueId())) {
island.clearChanged();
handler.saveObjectAsync(island)
.thenAccept(b -> MultiLib.notify("bentobox-updateIsland", island.getUniqueId()));
}
}
/**
@ -1622,108 +1665,6 @@ public class IslandsManager {
return Optional.ofNullable(islandCache.getIslandById(uniqueId));
}
/**
* Try to get an unmodifiable list of quarantined islands owned by uuid in this
* world
*
* @param world - world
* @param uuid - target player's UUID, or <tt>null</tt> = unowned islands
* @return list of islands; may be empty
* @since 1.3.0
*/
@NonNull
public List<Island> getQuarantinedIslandByUser(@NonNull World world, @Nullable UUID uuid) {
return quarantineCache.getOrDefault(uuid, Collections.emptyList()).stream()
.filter(i -> i.getWorld().equals(world)).toList();
}
/**
* Delete quarantined islands owned by uuid in this world
*
* @param world - world
* @param uuid - target player's UUID, or <tt>null</tt> = unowned islands
* @since 1.3.0
*/
public void deleteQuarantinedIslandByUser(World world, @Nullable UUID uuid) {
if (quarantineCache.containsKey(uuid)) {
quarantineCache.get(uuid).stream().filter(i -> i.getWorld().equals(world))
.forEach(i -> handler.deleteObject(i));
quarantineCache.get(uuid).removeIf(i -> i.getWorld().equals(world));
}
}
/**
* @return the quarantineCache
* @since 1.3.0
*/
@NonNull
public Map<UUID, List<Island>> getQuarantineCache() {
return quarantineCache;
}
/**
* Remove a quarantined island and delete it from the database completely. This
* is NOT recoverable unless you have database backups.
*
* @param island island
* @return {@code true} if island is quarantined and removed
* @since 1.3.0
*/
public boolean purgeQuarantinedIsland(Island island) {
if (quarantineCache.containsKey(island.getOwner()) && quarantineCache.get(island.getOwner()).remove(island)) {
handler.deleteObject(island);
return true;
}
return false;
}
/**
* Switches active island and island in trash
*
* @param world - game world
* @param target - target player's UUID
* @param island - island in trash
* @return <tt>true</tt> if successful, otherwise <tt>false</tt>
* @since 1.3.0
*/
public boolean switchIsland(World world, UUID target, Island island) {
// Remove trashed island from trash
if (!quarantineCache.containsKey(island.getOwner()) || !quarantineCache.get(island.getOwner()).remove(island)) {
plugin.logError("Could not remove island from trash");
return false;
}
// Remove old island from cache if it exists
if (this.hasIsland(world, target)) {
Island oldIsland = islandCache.get(world, target);
islandCache.removeIsland(oldIsland);
// Set old island to trash
oldIsland.setDoNotLoad(true);
// Put old island into trash
quarantineCache.computeIfAbsent(target, k -> new ArrayList<>()).add(oldIsland);
// Save old island
handler.saveObjectAsync(oldIsland).thenAccept(result -> {
if (Boolean.FALSE.equals(result))
plugin.logError("Could not save trashed island in database");
});
}
// Restore island from trash
island.setDoNotLoad(false);
// Add new island to cache
if (!islandCache.addIsland(island)) {
plugin.logError("Could not add recovered island to cache");
return false;
}
// Save new island
handler.saveObjectAsync(island).thenAccept(result -> {
if (Boolean.FALSE.equals(result)) {
plugin.logError("Could not save recovered island to database");
}
});
return true;
}
/**
* Resets all flags to gamemode config.yml default
*
@ -1747,121 +1688,6 @@ public class IslandsManager {
this.saveAll();
}
/**
* Returns whether the specified island custom name exists in this world.
*
* @param world World of the gamemode
* @param name Name of an island
* @return {@code true} if there is an island with the specified name in this
* world, {@code false} otherwise.
* @since 1.7.0
*/
public boolean nameExists(@NonNull World world, @NonNull String name) {
return getIslands(world).stream().map(Island::getName).filter(Objects::nonNull)
.anyMatch(n -> ChatColor.stripColor(n).equals(ChatColor.stripColor(name)));
}
/**
* Called by the admin team fix command. Attempts to fix the database for teams.
* It will identify and correct situations where a player is listed in multiple
* teams, or is the owner of multiple teams. It will also try to fix the current
* cache. It is recommended to restart the server after this command is run.
*
* @param user - admin calling
* @param world - game world to check
* @return CompletableFuture boolean - true when done
* @deprecated Not compatible with multi-islands. Will be removed.
*/
@Deprecated
public CompletableFuture<Boolean> checkTeams(User user, World world) {
CompletableFuture<Boolean> r = new CompletableFuture<>();
user.sendMessage("commands.admin.team.fix.scanning");
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Map<UUID, Island> owners = new HashMap<>();
Map<UUID, Integer> freq = new HashMap<>();
Map<UUID, List<Island>> memberships = new HashMap<>();
handler.loadObjects().stream().filter(i -> i.getOwner() != null).filter(i -> i.getWorld() != null)
.filter(i -> i.getWorld().equals(world)).filter(i -> !i.isDoNotLoad()).forEach(i -> {
int count = freq.getOrDefault(i.getOwner(), 0);
freq.put(i.getOwner(), count + 1);
if (owners.containsKey(i.getOwner())) {
// Player already has an island in the database
user.sendMessage("commands.admin.team.fix.duplicate-owner", TextVariables.NAME,
plugin.getPlayers().getName(i.getOwner()));
Island prev = owners.get(i.getOwner());
// Find out if this island is in the cache
Island cachedIsland = this.getIsland(i.getWorld(), i.getOwner());
if (cachedIsland != null && !cachedIsland.getUniqueId().equals(i.getUniqueId())) {
islandCache.deleteIslandFromCache(i.getUniqueId());
handler.deleteID(i.getUniqueId());
}
if (cachedIsland != null && !cachedIsland.getUniqueId().equals(prev.getUniqueId())) {
islandCache.deleteIslandFromCache(prev.getUniqueId());
handler.deleteID(prev.getUniqueId());
}
} else {
owners.put(i.getOwner(), i);
i.getMemberSet().forEach(u ->
// Place into membership
memberships.computeIfAbsent(u, k -> new ArrayList<>()).add(i));
}
});
freq.entrySet().stream().filter(en -> en.getValue() > 1)
.forEach(en -> user.sendMessage("commands.admin.team.fix.player-has", TextVariables.NAME,
plugin.getPlayers().getName(en.getKey()), TextVariables.NUMBER,
String.valueOf(en.getValue())));
// Check for players in multiple teams
memberships.entrySet().stream().filter(en -> en.getValue().size() > 1).forEach(en -> {
// Get the islands
String ownerName = plugin.getPlayers().getName(en.getKey());
user.sendMessage("commands.admin.team.fix.duplicate-member", TextVariables.NAME, ownerName);
int highestRank = 0;
Island highestIsland = null;
for (Island i : en.getValue()) {
int rankValue = i.getRank(en.getKey());
String rank = RanksManager.getInstance().getRank(rankValue);
if (rankValue > highestRank || highestIsland == null) {
highestRank = rankValue;
highestIsland = i;
}
String xyz = Util.xyz(i.getCenter().toVector());
user.sendMessage("commands.admin.team.fix.rank-on-island", TextVariables.RANK,
user.getTranslation(rank), TextVariables.XYZ, xyz);
user.sendRawMessage(i.getUniqueId());
}
// Fix island ownership in cache
// Correct island cache
if (highestRank == RanksManager.OWNER_RANK && highestIsland != null
&& islandCache.getIslandById(highestIsland.getUniqueId()) != null) {
islandCache.setOwner(islandCache.getIslandById(highestIsland.getUniqueId()), en.getKey());
}
// Fix all the entries that are not the highest
for (Island island : en.getValue()) {
if (!island.equals(highestIsland)) {
// Get the actual island being used in the cache
Island i = islandCache.getIslandById(island.getUniqueId());
if (i != null) {
// Remove membership of this island
i.removeMember(en.getKey());
}
// Remove from database island
island.removeMember(en.getKey());
// Save to database
handler.saveObjectAsync(island)
.thenRun(() -> user.sendMessage("commands.admin.team.fix.fixed"));
} else {
// Special check for when a player is an owner and member
}
}
});
user.sendMessage("commands.admin.team.fix.done");
r.complete(true);
});
return r;
}
/**
* Is user mid home teleport?
*
@ -1893,14 +1719,14 @@ public class IslandsManager {
}
/**
* Convenience method. See {@link IslandCache#get(World, UUID)}
* Convenience method. See {@link IslandCache#getIsland(World, UUID)}
*
* @param world world
* @param uuid player's UUID
* @return Island of player or null if there isn't one
*/
public Island getPrimaryIsland(World world, UUID uuid) {
return this.getIslandCache().get(world, uuid);
return this.getIslandCache().getIsland(world, uuid);
}
}

View File

@ -3,6 +3,9 @@ package world.bentobox.bentobox.managers;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
@ -13,6 +16,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.placeholders.PlaceholderReplacer;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
@ -72,6 +76,50 @@ public class PlaceholdersManager {
.forEach(placeholder -> registerPlaceholder(addon, placeholder.getPlaceholder(), new DefaultPlaceholder(addon, placeholder)));
// Register team member placeholders
registerTeamMemberPlaceholders(addon);
// Register potential island names and member info
registerOwnedIslandPlaceholders(addon);
}
private void registerOwnedIslandPlaceholders(@NonNull GameModeAddon addon) {
int maxIslands = plugin.getIWM().getWorldSettings(addon.getOverWorld()).getConcurrentIslands();
IntStream.range(0, maxIslands).forEach(i -> registerPlaceholder(addon, "island_name_" + (i + 1), user -> {
if (user == null)
return "";
AtomicInteger generatedCount = new AtomicInteger(1); // To increment within lambda
return plugin.getIslands().getIslands(addon.getOverWorld(), user).stream().map(island -> {
IslandName islandName = getIslandName(island, user, generatedCount.get());
if (islandName.generatated()) {
generatedCount.getAndIncrement(); // Increment if the name was generated
}
return islandName.name;
}).skip(i) // Skip to the island at index 'i'
.findFirst() // Take the first island after skipping, effectively the (i+1)th
.orElse(""); // Default to empty string if no island is found
}));
// Island_memberlist
IntStream.range(0, maxIslands)
.forEach(i -> registerPlaceholder(addon, "island_memberlist_" + (i + 1), user -> user == null ? ""
: plugin.getIslands().getIslands(addon.getOverWorld(), user).stream().skip(i).findFirst()
.map(island -> island.getMemberSet().stream()
.map(addon.getPlayers()::getName).collect(Collectors.joining(",")))
.orElse("")));
}
private record IslandName(String name, boolean generatated) {
}
private IslandName getIslandName(Island island, User user, int index) {
if (island.getName() != null && !island.getName().isBlank()) {
// Name has been set
return new IslandName(island.getName(), false);
} else {
// Name has not been set
return new IslandName(user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME,
user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index, true);
}
}
private void registerTeamMemberPlaceholders(@NonNull GameModeAddon addon) {

View File

@ -4,21 +4,18 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.scheduler.BukkitRunnable;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
@ -31,11 +28,9 @@ public class PlayersManager {
private final BentoBox plugin;
private Database<Players> handler;
private final Database<Names> names;
private final Map<UUID, Players> playerCache = new HashMap<>();
private final Map<UUID, Players> playerCache;
private final Set<UUID> inTeleport;
private boolean isSaveTaskRunning;
private final Set<UUID> inTeleport; // this needs databasing
/**
* Provides a memory cache of online player information
@ -50,7 +45,6 @@ public class PlayersManager {
handler = new Database<>(plugin, Players.class);
// Set up the names database
names = new Database<>(plugin, Names.class);
playerCache = new HashMap<>();
inTeleport = new HashSet<>();
}
@ -62,67 +56,7 @@ public class PlayersManager {
this.handler = handler;
}
/**
* Load all players - not normally used as to load all players into memory will be wasteful
*/
public void load(){
playerCache.clear();
inTeleport.clear();
handler.loadObjects().forEach(p -> playerCache.put(p.getPlayerUUID(), p));
}
public boolean isSaveTaskRunning() {
return isSaveTaskRunning;
}
/**
* Save all players
*/
public void saveAll() {
saveAll(false);
}
/**
* Save all players
* @param schedule true if we should let the task run over multiple ticks to reduce lag spikes
*/
public void saveAll(boolean schedule){
if (!schedule) {
for (Players player : playerCache.values()) {
try {
handler.saveObjectAsync(player);
} catch (Exception e) {
plugin.logError("Could not save player to database when running sync! " + e.getMessage());
}
}
return;
}
isSaveTaskRunning = true;
Queue<Players> queue = new LinkedList<>(playerCache.values());
new BukkitRunnable() {
@Override
public void run() {
for (int i = 0; i < plugin.getSettings().getMaxSavedPlayersPerTick(); i++) {
Players player = queue.poll();
if (player == null) {
isSaveTaskRunning = false;
cancel();
return;
}
try {
handler.saveObjectAsync(player);
} catch (Exception e) {
plugin.logError("Could not save player to database when running sync! " + e.getMessage());
}
}
}
}.runTaskTimer(plugin, 0, 1);
}
public void shutdown(){
saveAll();
playerCache.clear();
handler.close();
}
@ -133,10 +67,29 @@ public class PlayersManager {
*/
@Nullable
public Players getPlayer(UUID uuid){
if (!playerCache.containsKey(uuid)) {
addPlayer(uuid);
return playerCache.computeIfAbsent(uuid, this::addPlayer);
}
/**
* Adds a player to the database. If the UUID does not exist, a new player is created.
*
* @param playerUUID the player's UUID, must not be null
* @return the loaded or newly created player
* @throws NullPointerException if playerUUID is null
*/
private Players addPlayer(@NonNull UUID playerUUID) {
Objects.requireNonNull(playerUUID, "Player UUID must not be null");
// If the player exists in the database, load it; otherwise, create and save a new player
if (handler.objectExists(playerUUID.toString())) {
Players player = handler.loadObject(playerUUID.toString());
if (player != null) {
return player;
}
}
return playerCache.get(uuid);
Players newPlayer = new Players(plugin, playerUUID);
handler.saveObjectAsync(newPlayer);
return newPlayer;
}
/**
@ -146,37 +99,7 @@ public class PlayersManager {
*/
@NonNull
public Collection<Players> getPlayers() {
return Collections.unmodifiableCollection(playerCache.values());
}
/*
* Cache control methods
*/
/**
* Adds a player to the cache. If the UUID does not exist, a new player is made
* @param playerUUID - the player's UUID
*/
public void addPlayer(UUID playerUUID) {
if (playerUUID == null) {
return;
}
if (!playerCache.containsKey(playerUUID)) {
Players player;
// If the player is in the database, load it, otherwise create a new player
if (handler.objectExists(playerUUID.toString())) {
player = handler.loadObject(playerUUID.toString());
if (player == null) {
player = new Players(plugin, playerUUID);
// Corrupted database entry
plugin.logError("Corrupted player database entry for " + playerUUID + " - unrecoverable. Recreated.");
player.setUniqueId(playerUUID.toString());
}
} else {
player = new Players(plugin, playerUUID);
}
playerCache.put(playerUUID, player);
}
return Collections.unmodifiableCollection(handler.loadObjects());
}
/**
@ -187,7 +110,7 @@ public class PlayersManager {
* @return true if player is known, otherwise false
*/
public boolean isKnown(UUID uniqueID) {
return uniqueID != null && (playerCache.containsKey(uniqueID) || handler.objectExists(uniqueID.toString()));
return uniqueID == null ? false : handler.objectExists(uniqueID.toString());
}
/**
@ -206,11 +129,8 @@ public class PlayersManager {
// Not used
}
}
// Look in the name cache, then the data base and then give up
return playerCache.values().stream()
.filter(p -> p.getPlayerName().equalsIgnoreCase(name)).findFirst()
.map(p -> UUID.fromString(p.getUniqueId()))
.orElseGet(() -> names.objectExists(name) ? names.loadObject(name).getUuid() : null);
return names.loadObjects().stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst()
.map(Names::getUuid).orElse(null);
}
/**
@ -218,8 +138,9 @@ public class PlayersManager {
* @param user - the User
*/
public void setPlayerName(@NonNull User user) {
addPlayer(user.getUniqueId());
playerCache.get(user.getUniqueId()).setPlayerName(user.getName());
Players player = getPlayer(user.getUniqueId());
player.setPlayerName(user.getName());
handler.saveObject(player);
Names newName = new Names(user.getName(), user.getUniqueId());
// Add to names database
names.saveObjectAsync(newName);
@ -237,8 +158,8 @@ public class PlayersManager {
if (playerUUID == null) {
return "";
}
addPlayer(playerUUID);
return playerCache.get(playerUUID).getPlayerName();
getPlayer(playerUUID);
return Objects.requireNonNullElse(playerCache.get(playerUUID).getPlayerName(), "");
}
/**
@ -248,8 +169,7 @@ public class PlayersManager {
* @return number of resets
*/
public int getResets(World world, UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID).getResets(world);
return getPlayer(playerUUID).getResets(world);
}
/**
@ -261,7 +181,7 @@ public class PlayersManager {
* @see #getResets(World, UUID)
*/
public int getResetsLeft(World world, UUID playerUUID) {
addPlayer(playerUUID);
getPlayer(playerUUID);
if (plugin.getIWM().getResetLimit(world) == -1) {
return -1;
} else {
@ -277,8 +197,9 @@ public class PlayersManager {
* @param resets number of resets to set
*/
public void setResets(World world, UUID playerUUID, int resets) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setResets(world, resets);
Players p = getPlayer(playerUUID);
p.setResets(world, resets);
handler.saveObject(p);
}
/**
@ -287,11 +208,7 @@ public class PlayersManager {
* @return name of the locale this player uses
*/
public String getLocale(UUID playerUUID) {
addPlayer(playerUUID);
if (playerUUID == null) {
return "";
}
return playerCache.get(playerUUID).getLocale();
return getPlayer(playerUUID).getLocale();
}
/**
@ -300,8 +217,9 @@ public class PlayersManager {
* @param localeName - locale name, e.g., en-US
*/
public void setLocale(UUID playerUUID, String localeName) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setLocale(localeName);
Players p = getPlayer(playerUUID);
p.setLocale(localeName);
handler.saveObject(p);
}
/**
@ -310,8 +228,9 @@ public class PlayersManager {
* @param playerUUID - the player's UUID
*/
public void addDeath(World world, UUID playerUUID) {
addPlayer(playerUUID);
playerCache.get(playerUUID).addDeath(world);
Players p = getPlayer(playerUUID);
p.addDeath(world);
handler.saveObject(p);
}
/**
@ -321,8 +240,9 @@ public class PlayersManager {
* @param deaths - number of deaths
*/
public void setDeaths(World world, UUID playerUUID, int deaths) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setDeaths(world, deaths);
Players p = getPlayer(playerUUID);
p.setDeaths(world, deaths);
handler.saveObject(p);
}
/**
@ -332,8 +252,7 @@ public class PlayersManager {
* @return number of deaths
*/
public int getDeaths(World world, UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID) == null ? 0 : playerCache.get(playerUUID).getDeaths(world);
return getPlayer(playerUUID).getDeaths(world);
}
/**
@ -360,16 +279,6 @@ public class PlayersManager {
return inTeleport.contains(uniqueId);
}
/**
* Saves the player to the database
* @param playerUUID - the player's UUID
*/
public void save(UUID playerUUID) {
if (playerCache.containsKey(playerUUID)) {
handler.saveObjectAsync(playerCache.get(playerUUID));
}
}
/**
* Tries to get the user from his name
* @param name - name
@ -395,41 +304,17 @@ public class PlayersManager {
* @param playerUUID player's UUID
*/
public void addReset(World world, UUID playerUUID) {
addPlayer(playerUUID);
playerCache.get(playerUUID).addReset(world);
Players p = getPlayer(playerUUID);
p.addReset(world);
handler.saveObject(p);
}
/**
* Sets the Flags display mode for the Settings Panel for this player.
* @param playerUUID player's UUID
* @param displayMode the {@link Flag.Mode} to set
* @since 1.6.0
*/
public void setFlagsDisplayMode(UUID playerUUID, Flag.Mode displayMode) {
addPlayer(playerUUID);
playerCache.get(playerUUID).setFlagsDisplayMode(displayMode);
}
/**
* Returns the Flags display mode for the Settings Panel for this player.
* @param playerUUID player's UUID
* @return the {@link Flag.Mode display mode} for the Flags in the Settings Panel.
* @since 1.6.0
*/
public Flag.Mode getFlagsDisplayMode(UUID playerUUID) {
addPlayer(playerUUID);
return playerCache.get(playerUUID).getFlagsDisplayMode();
}
/**
* Remove player from cache. Clears players with the same name or UUID
* Remove player from database
* @param player player to remove
*/
public void removePlayer(Player player) {
// Clear any players with the same name
playerCache.values().removeIf(p -> player.getName().equalsIgnoreCase(p.getPlayerName()));
// Remove if the player's UUID is the same
playerCache.values().removeIf(p -> player.getUniqueId().toString().equals(p.getUniqueId()));
handler.deleteID(player.getUniqueId().toString());
}
/**
@ -495,8 +380,6 @@ public class PlayersManager {
// Player total XP (not displayed)
target.getPlayer().setTotalExperience(0);
}
// Save player
save(target.getUniqueId());
}
}

View File

@ -1,15 +1,21 @@
package world.bentobox.bentobox.managers.island;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
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.eclipse.jdt.annotation.NonNull;
@ -17,7 +23,9 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
@ -26,8 +34,6 @@ import world.bentobox.bentobox.util.Util;
* @author tastybento
*/
public class IslandCache {
@NonNull
private final Map<@NonNull Location, @NonNull Island> islandsByLocation;
/**
* Map of all islands with island uniqueId as key
*/
@ -38,39 +44,73 @@ public class IslandCache {
* UUID, value is a set of islands
*/
@NonNull
private final Map<@NonNull UUID, Set<Island>> islandsByUUID;
private final Map<@NonNull UUID, Set<String>> islandsByUUID;
@NonNull
private final Map<@NonNull World, @NonNull IslandGrid> grids;
private final @NonNull Database<Island> handler;
public IslandCache() {
islandsByLocation = new HashMap<>();
public IslandCache(@NonNull Database<Island> handler) {
islandsById = new HashMap<>();
islandsByUUID = new HashMap<>();
grids = new HashMap<>();
this.handler = handler;
}
/**
* Adds an island to the grid
* Replace the island we have with this one
* @param newIsland island
*/
public void updateIsland(@NonNull Island newIsland) {
if (newIsland.isDeleted()) {
this.deleteIslandFromCache(newIsland);
return;
}
// Get the old island
Island oldIsland = getIslandById(newIsland.getUniqueId());
Set<UUID> newMembers = newIsland.getMembers().keySet();
if (oldIsland != null) {
Set<UUID> oldMembers = oldIsland.getMembers().keySet();
// Remove any members who are not in the new island
for (UUID oldMember : oldMembers) {
if (!newMembers.contains(oldMember)) {
// Member has been removed - remove island
islandsByUUID.computeIfAbsent(oldMember, k -> new HashSet<>()).remove(oldIsland.getUniqueId());
}
}
}
// Update the members with the new island object
for (UUID newMember : newMembers) {
Set<String> set = islandsByUUID.computeIfAbsent(newMember, k -> new HashSet<>());
if (oldIsland != null) {
set.remove(oldIsland.getUniqueId());
}
set.add(newIsland.getUniqueId());
islandsByUUID.put(newMember, set);
}
if (setIslandById(newIsland) == null) {
BentoBox.getInstance().logError("islandsById failed to update");
}
}
/**
* Adds an island to the grid, used for new islands
*
* @param island island to add, not null
* @return true if successfully added, false if not
*/
public boolean addIsland(@NonNull Island island) {
if (island.getCenter() == null || island.getWorld() == null) {
/*
* Special handling - return true. The island will not be quarantined, but just
* not loaded This can occur when a gamemode is removed temporarily from the
* server TODO: have an option to remove these when the purge command is added
*/
return true;
return false;
}
if (addToGrid(island)) {
islandsByLocation.put(island.getCenter(), island);
islandsById.put(island.getUniqueId(), island);
// Insert a null into the map as a placeholder for cache
islandsById.put(island.getUniqueId().intern(), null);
// Only add islands to this map if they are owned
if (island.isOwned()) {
islandsByUUID.computeIfAbsent(island.getOwner(), k -> new HashSet<>()).add(island);
islandsByUUID.computeIfAbsent(island.getOwner(), k -> new HashSet<>()).add(island.getUniqueId());
island.getMemberSet().forEach(member -> addPlayer(member, island));
}
return true;
@ -79,14 +119,14 @@ public class IslandCache {
}
/**
* Adds a player's UUID to the look up for islands. Does no checking
* Adds a player's UUID to the look up for islands. Does no checking. The island for this player must have been added beforehand.
*
* @param uuid player's uuid
* @param island island to associate with this uuid. Only one island can be
* associated per world.
*/
public void addPlayer(@NonNull UUID uuid, @NonNull Island island) {
islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).add(island);
this.islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).add(island.getUniqueId());
}
/**
@ -96,11 +136,10 @@ public class IslandCache {
* @return true if successfully added, false if not
*/
private boolean addToGrid(@NonNull Island newIsland) {
return grids.computeIfAbsent(newIsland.getWorld(), k -> new IslandGrid()).addToGrid(newIsland);
return grids.computeIfAbsent(newIsland.getWorld(), k -> new IslandGrid(this)).addToGrid(newIsland);
}
public void clear() {
islandsByLocation.clear();
islandsById.clear();
islandsByUUID.clear();
}
@ -109,29 +148,19 @@ public class IslandCache {
* Deletes an island from the cache. Does not remove blocks.
*
* @param island island to delete
* @return true if successful, false if not
*/
public boolean deleteIslandFromCache(@NonNull Island island) {
if (!islandsByLocation.remove(island.getCenter(), island)) {
return false;
}
islandsById.remove(island.getUniqueId());
public void deleteIslandFromCache(@NonNull Island island) {
islandsById.remove(island.getUniqueId(), island);
removeFromIslandsByUUID(island);
// Remove from grid
grids.putIfAbsent(island.getWorld(), new IslandGrid());
return grids.get(island.getWorld()).removeFromGrid(island);
if (grids.containsKey(island.getWorld())) {
grids.get(island.getWorld()).removeFromGrid(island);
}
}
private void removeFromIslandsByUUID(Island island) {
for (Set<Island> set : islandsByUUID.values()) {
Iterator<Island> is = set.iterator();
while (is.hasNext()) {
Island i = is.next();
if (i.equals(island)) {
is.remove();
}
}
// set.removeIf(island::equals);
for (Set<String> set : islandsByUUID.values()) {
set.removeIf(island.getUniqueId()::equals);
}
}
@ -141,24 +170,11 @@ public class IslandCache {
* @param uniqueId - island unique ID
*/
public void deleteIslandFromCache(@NonNull String uniqueId) {
islandsById.remove(uniqueId);
islandsByLocation.values().removeIf(i -> i.getUniqueId().equals(uniqueId));
for (Set<Island> set : islandsByUUID.values()) {
set.removeIf(i -> i.getUniqueId().equals(uniqueId));
if (islandsById.containsKey(uniqueId)) {
deleteIslandFromCache(getIslandById(uniqueId));
}
}
/**
* Get island based on the exact center location of the island
*
* @param location location to search for
* @return island or null if it does not exist
*/
@Nullable
public Island get(@NonNull Location location) {
return islandsByLocation.get(location);
}
/**
* Returns island referenced by player's UUID. Returns the island the player is
* on now, or their last known island
@ -168,19 +184,19 @@ public class IslandCache {
* @return island or null if none
*/
@Nullable
public Island get(@NonNull World world, @NonNull UUID uuid) {
Set<Island> islands = getIslands(world, uuid);
public Island getIsland(@NonNull World world, @NonNull UUID uuid) {
List<Island> islands = getIslands(world, uuid);
if (islands.isEmpty()) {
return null;
}
for (Island island : islands) {
if (island.isPrimary()) {
if (island.isPrimary(uuid)) {
return island;
}
}
// If there is no primary set, then set one - it doesn't matter which.
Island result = islands.iterator().next();
result.setPrimary(true);
result.setPrimary(uuid);
return result;
}
@ -189,15 +205,17 @@ public class IslandCache {
*
* @param world world to check. Includes nether and end worlds.
* @param uuid player's UUID
* @return list of island or empty list if none
* @return list of island or empty list if none sorted from oldest to youngest
*/
public Set<Island> getIslands(@NonNull World world, @NonNull UUID uuid) {
public List<Island> getIslands(@NonNull World world, @NonNull UUID uuid) {
World w = Util.getWorld(world);
if (w == null) {
return new HashSet<>();
return new ArrayList<>();
}
return islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).stream().filter(i -> w.equals(i.getWorld()))
.collect(Collectors.toSet());
return islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).stream().map(this::getIslandById)
.filter(Objects::nonNull).filter(island -> w.equals(island.getWorld()))
.sorted(Comparator.comparingLong(Island::getCreatedDate))
.collect(Collectors.toList());
}
/**
@ -207,8 +225,16 @@ public class IslandCache {
* @param island island to make primary
*/
public void setPrimaryIsland(@NonNull UUID uuid, @NonNull Island island) {
if (island.getPrimaries().contains(uuid)) {
return;
}
for (Island is : getIslands(island.getWorld(), uuid)) {
is.setPrimary(island.equals(is));
if (is.getPrimaries().contains(uuid)) {
is.removePrimary(uuid);
}
if (is.equals(island)) {
is.setPrimary(uuid);
}
}
}
@ -230,18 +256,40 @@ public class IslandCache {
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those who may be unowned).
* those who may be unowned). Gets them from the cache or from the database if not
* loaded.
*
* @return unmodifiable collection containing every island.
*/
@NonNull
public Collection<Island> getIslands() {
return Collections.unmodifiableCollection(islandsByLocation.values());
List<Island> result = new ArrayList<>();
for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
Island island = entry.getValue() != null ? entry.getValue() : handler.loadObject(entry.getKey());
if (island != null) {
result.add(island);
}
}
return Collections.unmodifiableCollection(result);
}
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those who may be unowned) in the specified world.
* those who may be unowned) that are cached.
*
* @return unmodifiable collection containing every cached island.
*/
@NonNull
public Collection<Island> getCachedIslands() {
return islandsById.entrySet().stream().filter(en -> Objects.nonNull(en.getValue())).map(Map.Entry::getValue)
.toList();
}
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those that may be unowned) in the specified world.
* Gets islands from the cache if they have been loaded, or from the database if not
*
* @param world World of the gamemode.
* @return unmodifiable collection containing all the islands in the specified
@ -254,9 +302,16 @@ public class IslandCache {
if (overworld == null) {
return Collections.emptyList();
}
return islandsByLocation.entrySet().stream()
.filter(entry -> overworld.equals(Util.getWorld(entry.getKey().getWorld()))) // shouldn't make NPEs
.map(Map.Entry::getValue).toList();
List<Island> result = new ArrayList<>();
for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
Island island = entry.getValue() != null ? entry.getValue() : handler.loadObject(entry.getKey());
if (island != null && overworld.equals(island.getWorld())) {
result.add(island);
}
}
return Collections.unmodifiableCollection(result);
}
/**
@ -271,7 +326,8 @@ public class IslandCache {
if (!islandsByUUID.containsKey(uuid)) {
return false;
}
return this.islandsByUUID.get(uuid).stream().filter(i -> world.equals(i.getWorld()))
return this.islandsByUUID.get(uuid).stream().map(this::getIslandById).filter(Objects::nonNull)
.filter(i -> world.equals(i.getWorld()))
.anyMatch(i -> uuid.equals(i.getOwner()));
}
@ -281,20 +337,24 @@ public class IslandCache {
*
* @param world world
* @param uuid player's UUID
* @return list of islands player had or empty if none
* @return set of islands player had or empty if none
*/
public Set<Island> removePlayer(@NonNull World world, @NonNull UUID uuid) {
World w = Util.getWorld(world);
Set<Island> islandSet = islandsByUUID.get(uuid);
if (w == null || islandSet == null) {
return Collections.emptySet(); // Return empty list if no islands map exists for the world
World resolvedWorld = Util.getWorld(world);
Set<String> playerIslandIds = islandsByUUID.get(uuid);
Set<Island> removedIslands = new HashSet<>();
if (resolvedWorld == null || playerIslandIds == null) {
return Collections.emptySet(); // Return empty set if no islands map exists for the world
}
// Go through all the islands associated with this player in this world and
// remove the player from them.
Iterator<Island> it = islandSet.iterator();
while (it.hasNext()) {
Island island = it.next();
if (w.equals(island.getWorld())) {
// Iterate over the player's island IDs and process each associated island
Iterator<String> iterator = playerIslandIds.iterator();
while (iterator.hasNext()) {
Island island = this.getIslandById(iterator.next());
if (island != null && resolvedWorld.equals(island.getWorld())) {
removedIslands.add(island);
if (uuid.equals(island.getOwner())) {
// Player is the owner, so clear the whole island and clear the ownership
island.getMembers().clear();
@ -302,11 +362,13 @@ public class IslandCache {
} else {
island.removeMember(uuid);
}
// Remove this island from this set of islands associated to this player
it.remove();
// Remove this island from the set of islands associated with this player
iterator.remove();
}
}
return islandSet;
return removedIslands;
}
/**
@ -316,11 +378,12 @@ public class IslandCache {
* @param uuid uuid of member to remove
*/
public void removePlayer(@NonNull Island island, @NonNull UUID uuid) {
Set<Island> islandSet = islandsByUUID.get(uuid);
Set<String> islandSet = islandsByUUID.get(uuid);
if (islandSet != null) {
islandSet.remove(island);
islandSet.remove(island.getUniqueId());
}
island.removeMember(uuid);
island.removePrimary(uuid);
}
/**
@ -329,17 +392,17 @@ public class IslandCache {
* @return the number of islands
*/
public int size() {
return islandsByLocation.size();
return islandsById.size();
}
/**
* Gets the number of islands in the cache for this world
* Gets the number of islands in this world
*
* @param world world to get the number of islands in
* @return the number of islands
*/
public long size(World world) {
return this.islandsByLocation.keySet().stream().map(Location::getWorld).filter(world::equals).count();
return this.islandsById.values().stream().map(Island::getWorld).filter(world::equals).count();
}
/**
@ -351,10 +414,10 @@ public class IslandCache {
public void setOwner(@NonNull Island island, @Nullable UUID newOwnerUUID) {
island.setOwner(newOwnerUUID);
if (newOwnerUUID != null) {
islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island);
islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island.getUniqueId());
}
islandsByLocation.put(island.getCenter(), island);
islandsById.put(island.getUniqueId(), island);
island.setRank(newOwnerUUID, RanksManager.OWNER_RANK);
setIslandById(island);
}
/**
@ -366,42 +429,28 @@ public class IslandCache {
*/
@Nullable
public Island getIslandById(@NonNull String uniqueId) {
return islandsById.get(uniqueId);
// Load from cache or database
return islandsById.computeIfAbsent(uniqueId, handler::loadObject);
}
/**
* Removes an island from the cache completely without altering the island
* object
*
* @param island - island to remove
* @since 1.3.0
* Place the island into the cache map
* @param island island
* @return the previous value associated with island, or null if this is a new entry
*/
public void removeIsland(@NonNull Island island) {
islandsByLocation.values().removeIf(island::equals);
islandsById.values().removeIf(island::equals);
islandsByUUID.values().removeIf(island::equals);
World w = Util.getWorld(island.getWorld());
if (w == null) {
return;
}
if (grids.containsKey(w)) {
grids.get(w).removeFromGrid(island);
}
Island setIslandById(Island island) {
return islandsById.put(island.getUniqueId().intern(), island);
}
/**
* Resets all islands in this game mode to default flag settings
* Resets all islands in this game mode to default flag settings.
*
* @param world - world
* @since 1.3.0
*/
public void resetAllFlags(World world) {
World w = Util.getWorld(world);
if (w == null) {
return;
}
islandsById.values().stream().filter(i -> i.getWorld().equals(w)).forEach(Island::setFlagsDefaults);
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(),
() -> this.getIslands(world).stream().forEach(Island::setFlagsDefaults));
}
/**
@ -412,13 +461,9 @@ public class IslandCache {
* @since 1.8.0
*/
public void resetFlag(World world, Flag flag) {
World w = Util.getWorld(world);
if (w == null) {
return;
}
int setting = BentoBox.getInstance().getIWM().getDefaultIslandFlags(w).getOrDefault(flag,
int setting = BentoBox.getInstance().getIWM().getDefaultIslandFlags(world).getOrDefault(flag,
flag.getDefaultRank());
islandsById.values().stream().filter(i -> i.getWorld().equals(w)).forEach(i -> i.setFlag(flag, setting));
this.getIslands(world).stream().forEach(i -> i.setFlag(flag, setting));
}
/**
@ -431,4 +476,13 @@ public class IslandCache {
return islandsById.keySet();
}
/**
* Get a unmodifiable list of all islands this player is involved with
* @param uniqueId player's UUID
* @return list of islands
*/
public @NonNull List<Island> getIslands(UUID uniqueId) {
return islandsByUUID.getOrDefault(uniqueId, Collections.emptySet()).stream().map(this::getIslandById).toList();
}
}

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