diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4191081bb..dff7a8f18 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -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:
diff --git a/README.md b/README.md
index 8989d0508..7cea761ef 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/pom.xml b/pom.xml
index 8aa65b84f..6efe8b389 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,10 +73,10 @@
42.2.18
5.0.1
- 1.20.4-R0.1-SNAPSHOT
+ 1.20.5-R0.1-SNAPSHOT
- 1.20.4-R0.1-SNAPSHOT
+ 1.20.6-R0.1-SNAPSHOT
3.0.0
1.7.1
2.10.9
@@ -88,7 +88,7 @@
-LOCAL
- 2.3.0
+ 2.4.0
bentobox-world
https://sonarcloud.io
${project.basedir}/lib
@@ -145,6 +145,10 @@
+
+ jitpack.io
+ https://jitpack.io
+
spigot-repo
https://hub.spigotmc.org/nexus/content/repositories/snapshots
@@ -153,10 +157,6 @@
codemc-repo
https://repo.codemc.org/repository/maven-public
-
- jitpack.io
- https://jitpack.io
-
placeholderapi-repo
https://repo.extendedclip.com/content/repositories/placeholderapi/
@@ -195,6 +195,11 @@
Lumine Releases
https://mvn.lumine.io/repository/maven-public/
+
+
+ clojars
+ https://repo.clojars.org/
+
@@ -223,13 +228,25 @@
3.11.1
test
-
+
org.spigotmc
spigot-api
${spigot.version}
provided
+
+ org.spigotmc.....
+ spigot
+ 1.21-R0.1-SNAPSHOT
+ provided
+
+
+ org.spigotmc....
+ spigot
+ 1.20.6-R0.1-SNAPSHOT
+ provided
+
org.spigotmc.
spigot
@@ -354,16 +371,23 @@
com.github.Slimefun
Slimefun4
- RC-36
+ RC-37
provided
com.github.LoneDev6
api-itemsadder
- 3.6.1
+ 3.6.3-beta-14
provided
+
+
+ com.github.puregero
+ multilib
+ 1.1.13
+ compile
+
@@ -486,9 +510,10 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.3.1-SNAPSHOT
+ 3.4.0
true
+ ${project.build.directory}/dependency-reduced-pom.xml
org.bstats
@@ -500,9 +525,13 @@
io.papermc.lib
- world.bentobox.bentobox.paperlib
+ world.bentobox.bentobox.paperlib
-
+
+ com.github.puregero.multilib
+ world.bentobox.bentobox.multilib
+
+
org.apache.maven.shared:*
diff --git a/src/main/java/world/bentobox/bentobox/BStats.java b/src/main/java/world/bentobox/bentobox/BStats.java
index aedf7c83a..12efe7865 100644
--- a/src/main/java/world/bentobox/bentobox/BStats.java
+++ b/src/main/java/world/bentobox/bentobox/BStats.java
@@ -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 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
diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index 235d37ef6..75a7c07a8 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -25,6 +25,7 @@ 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;
@@ -37,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;
@@ -102,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
@@ -123,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);
@@ -184,6 +187,9 @@ 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
@@ -210,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();
@@ -323,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
@@ -433,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();
}
diff --git a/src/main/java/world/bentobox/bentobox/api/addons/Addon.java b/src/main/java/world/bentobox/bentobox/api/addons/Addon.java
index 57190b7d9..c4807715e 100644
--- a/src/main/java/world/bentobox/bentobox/api/addons/Addon.java
+++ b/src/main/java/world/bentobox/bentobox/api/addons/Addon.java
@@ -21,11 +21,14 @@ 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;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
+import world.bentobox.bentobox.util.Util;
/**
* Add-on class for BentoBox. Extend this to create an add-on. The operation
@@ -45,6 +48,10 @@ public abstract class Addon {
protected Addon() {
state = State.DISABLED;
+ if (!Util.inTest()) {
+ // If the config is updated, update the config.
+ MultiLib.onString(getPlugin(), "bentobox-config-update", v -> this.reloadConfig());
+ }
}
/**
@@ -275,7 +282,7 @@ public abstract class Addon {
}
// There are two options, use the path of the resource or not
File outFile = new File(destinationFolder,
- jarResource.replaceAll("/", Matcher.quoteReplacement(File.separator)));
+ jarResource.replaceAll("/", Matcher.quoteReplacement(File.separator)));
if (noPath) {
outFile = new File(destinationFolder, outFile.getName());
@@ -396,7 +403,7 @@ public abstract class Addon {
public IslandsManager getIslands() {
return getPlugin().getIslands();
}
-
+
/**
* Get Islands Manager
* @return Islands manager
diff --git a/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java b/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java
index a45b12d68..79c3fa016 100644
--- a/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java
+++ b/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java
@@ -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
diff --git a/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java b/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java
index 55d0e2d49..af11005db 100644
--- a/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java
+++ b/src/main/java/world/bentobox/bentobox/api/addons/Pladdon.java
@@ -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();
}
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java
index 57081bac1..383f5bab2 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java
@@ -520,11 +520,11 @@ 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
* @return true if player is in a team
- * @see Consider checking the island itself {@link Island#inTeam(UUID)}
*/
protected boolean inTeam(World world, User user) {
return plugin.getIslands().inTeam(world, user.getUniqueId());
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminDeleteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminDeleteCommand.java
index 1c3a4c652..2ea7e5ded 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminDeleteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminDeleteCommand.java
@@ -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 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 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 (island.hasTeam() && 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 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 getNameIslandMap(User target) {
+ Map 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> tabComplete(User user, String alias, List 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 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();
}
+
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminEmptyTrashCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminEmptyTrashCommand.java
deleted file mode 100644
index f5c8970ff..000000000
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminEmptyTrashCommand.java
+++ /dev/null
@@ -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 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 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;
- }
- }
-}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommand.java
index ca3281996..dfb6eaf96 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminInfoCommand.java
@@ -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");
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
index 177d85657..f7fafe800 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java
@@ -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();
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSwitchtoCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSwitchtoCommand.java
deleted file mode 100644
index 3bb91c4c4..000000000
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSwitchtoCommand.java
+++ /dev/null
@@ -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 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 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 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;
- }
-
-}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
index 74dfe764e..162f6f222 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportCommand.java
@@ -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
@@ -37,16 +46,17 @@ public class AdminTeleportCommand extends CompositeCommand {
setPermission("admin.tp");
setParametersHelp("commands.admin.tp.parameters");
setDescription("commands.admin.tp.description");
+ this.setOnlyPlayer(true);
}
@Override
public boolean canExecute(User user, String label, List 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 +72,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 args) {
World world = getWorld();
if (getLabel().equals("tpnether")) {
world = getPlugin().getIWM().getNetherWorld(getWorld());
@@ -91,19 +82,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 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 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 +142,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 getNameIslandMap(User target) {
+ Map 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> tabComplete(User user, String alias, List args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
@@ -131,8 +174,17 @@ public class AdminTeleportCommand extends CompositeCommand {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
- List 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();
}
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportUserCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportUserCommand.java
new file mode 100644
index 000000000..d1880ea4e
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTeleportUserCommand.java
@@ -0,0 +1,200 @@
+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;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+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;
+import world.bentobox.bentobox.util.teleport.SafeSpotTeleport;
+
+/**
+ * Enables admins to teleport a player to another player's island, nether or end islands,
+ *
+ * For example /acid tp lspvicky tastybento [island name] would teleport lspvicky to tastybento's [named] island
+ *
+ */
+public class AdminTeleportUserCommand extends CompositeCommand {
+
+ private static final String NOT_SAFE = "general.errors.no-safe-location-found";
+ private Location warpSpot;
+ private @Nullable UUID targetUUID;
+ private @NonNull User toBeTeleported;
+
+ /**
+ * @param parent - parent command
+ * @param tpCommand - should be "tpuser", "tpusernether" or "tpuserend"
+ */
+ public AdminTeleportUserCommand(CompositeCommand parent, String tpCommand) {
+ super(parent, tpCommand);
+ }
+
+ @Override
+ public void setup() {
+ // Permission
+ setPermission("admin.tpuser");
+ setParametersHelp("commands.admin.tpuser.parameters");
+ setDescription("commands.admin.tpuser.description");
+ }
+
+ @Override
+ public boolean canExecute(User user, String label, List args) {
+ if (args.isEmpty() || args.size() == 1) {
+ this.showHelp(this, user);
+ return false;
+ }
+ // Convert first name to a UUID
+ UUID teleportee = Util.getUUID(args.get(0));
+ if (teleportee == null) {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+ // Check online
+ toBeTeleported = User.getInstance(teleportee);
+ if (!toBeTeleported.isOnline()) {
+ user.sendMessage("general.errors.offline-player");
+ return false;
+ }
+
+ // Convert second name to a UUID
+ targetUUID = Util.getUUID(args.get(1));
+ 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;
+ }
+
+ World world = getWorld();
+ if (getLabel().equals("tpusernether")) {
+ world = getPlugin().getIWM().getNetherWorld(getWorld());
+ } else if (getLabel().equals("tpuserend")) {
+ world = getPlugin().getIWM().getEndWorld(getWorld());
+ }
+ if (world == null) {
+ user.sendMessage(NOT_SAFE);
+ return false;
+ }
+ // Get default location if there are no arguments
+ warpSpot = getSpot(world);
+ if (warpSpot == null) {
+ user.sendMessage(NOT_SAFE);
+ return false;
+ }
+ if (args.size() == 2) {
+ return true;
+ }
+
+ // They named the island to go to
+ Map names = getNameIslandMap(User.getInstance(targetUUID));
+ final String name = String.join(" ", args.subList(2, 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 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 = toBeTeleported.getPlayer();
+ if (args.size() == 2) {
+ failureMessage = user.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 getNameIslandMap(User target) {
+ Map 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> tabComplete(User user, String alias, List 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() == 2 || args.size() == 3) {
+ return Optional.of(Util.tabLimit(new ArrayList<>(Util.getOnlinePlayerList(user)), lastArg));
+ }
+
+ if (args.size() == 4) {
+ UUID target = Util.getUUID(args.get(2));
+ return target == null ? Optional.empty()
+ : Optional
+ .of(Util.tabLimit(new ArrayList<>(getNameIslandMap(User.getInstance(target)).keySet()), lastArg));
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTrashCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTrashCommand.java
deleted file mode 100644
index e17d10041..000000000
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminTrashCommand.java
+++ /dev/null
@@ -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 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 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 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());
-
- }
-}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminUnregisterCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminUnregisterCommand.java
index 968ebf178..d4d730380 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminUnregisterCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminUnregisterCommand.java
@@ -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));
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java
index dbcd126de..be27bf080 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java
@@ -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;
@@ -51,9 +50,14 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
this.setDescription("commands.admin.help.description");
new AdminVersionCommand(this);
+
new AdminTeleportCommand(this, "tp");
new AdminTeleportCommand(this, "tpnether");
new AdminTeleportCommand(this, "tpend");
+ new AdminTeleportUserCommand(this, "tpuser");
+ new AdminTeleportUserCommand(this, "tpusernether");
+ new AdminTeleportUserCommand(this, "tpuserend");
+
new AdminGetrankCommand(this);
new AdminSetrankCommand(this);
new AdminInfoCommand(this);
@@ -63,7 +67,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
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java
index 8246d157d..9308301c5 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java
@@ -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 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);
}
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/conversations/NamePrompt.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/conversations/NamePrompt.java
index e0edd1b25..79776ed5b 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/conversations/NamePrompt.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/conversations/NamePrompt.java
@@ -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"));
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java
index a4218ac9d..ec02a496c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java
@@ -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 getOldIslands(int days) {
+ long currentTimeMillis = System.currentTimeMillis();
+ double daysInMilliseconds = days * 1000 * 3600 * 24;
+ Set 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;
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java
index f79049829..dbdf75342 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java
@@ -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 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));
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java
deleted file mode 100644
index b739b2a24..000000000
--- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java
+++ /dev/null
@@ -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 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 args) {
- getIslands().checkTeams(user, getWorld());
- return true;
- }
-}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
index d0582e787..798b91a2c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSetnameCommand.java
@@ -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;
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSettingsCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSettingsCommand.java
index e37fd8ff2..027a16961 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSettingsCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSettingsCommand.java
@@ -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()
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/Invite.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/Invite.java
deleted file mode 100644
index c6328d066..000000000
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/Invite.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
index fd1d06e82..6cb702ea7 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java
@@ -1,9 +1,7 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -16,17 +14,13 @@ import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.database.objects.TeamInvite;
import world.bentobox.bentobox.managers.RanksManager;
public class IslandTeamCommand extends CompositeCommand {
- /**
- * Invited list. Key is the invited party, value is the invite.
- * @since 1.8.0
- */
- private final Map inviteMap;
-
private IslandTeamKickCommand kickCommand;
private IslandTeamLeaveCommand leaveCommand;
@@ -51,9 +45,11 @@ public class IslandTeamCommand extends CompositeCommand {
private IslandTeamTrustCommand trustCommand;
+ private final Database handler;
+
public IslandTeamCommand(CompositeCommand parent) {
super(parent, "team");
- inviteMap = new HashMap<>();
+ handler = new Database<>(parent.getAddon(), TeamInvite.class);
}
@Override
@@ -139,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()));
}
/**
@@ -150,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());
}
/**
@@ -161,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;
}
/**
@@ -171,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());
}
/**
@@ -181,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());
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
index 246c38b21..45b84f904 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCoopCommand.java
@@ -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,
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java
index 598e940a9..c85ed76f8 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java
@@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
@@ -34,6 +33,8 @@ import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecord
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;
@@ -208,7 +209,7 @@ public class IslandTeamGUI {
private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
PanelItemBuilder builder = new PanelItemBuilder();
if (parent.isInvited(user.getUniqueId()) && user.hasPermission(parent.getAcceptCommand().getPermission())) {
- Invite invite = parent.getInvite(user.getUniqueId());
+ TeamInvite invite = parent.getInvite(user.getUniqueId());
if (invite == null) {
return this.getBlankBorder();
}
@@ -224,7 +225,8 @@ public class IslandTeamGUI {
return builder.build();
}
- private void createInviteClickHandler(PanelItemBuilder builder, Invite invite, @NonNull List list) {
+ private void createInviteClickHandler(PanelItemBuilder builder, TeamInvite invite,
+ @NonNull List list) {
Type type = invite.getType();
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
@@ -526,7 +528,6 @@ public class IslandTeamGUI {
/**
* 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
*/
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
index 88c91c162..3ba3013f1 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java
@@ -5,13 +5,14 @@ import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
-import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.database.objects.TeamInvite;
+import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@@ -49,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);
@@ -78,7 +79,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
@Override
public boolean execute(User user, String label, List 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));
@@ -94,11 +95,11 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand {
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)) {
@@ -120,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)) {
@@ -146,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
List 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;
@@ -196,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();
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
index 77a37dd30..82d2bf4dd 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java
@@ -10,13 +10,13 @@ import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.commands.CompositeCommand;
-import world.bentobox.bentobox.api.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.database.objects.TeamInvite.Type;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.managers.RanksManager;
@@ -166,7 +166,7 @@ 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());
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
index e80580aa9..16e23ee5c 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java
@@ -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;
}
diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
index 8d57871df..38c0349f7 100644
--- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
+++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamTrustCommand.java
@@ -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;
diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
index d28bc0add..89b13cb09 100644
--- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
+++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java
@@ -388,21 +388,24 @@ public class Flag implements Comparable {
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 {
private PanelItemBuilder createProtectionFlag(BentoBox plugin, User user, Island island, PanelItemBuilder pib) {
if (island != null) {
+ int y = island.getFlag(this);
// Protection flag
+
pib.description(user.getTranslation("protection.panel.flag-item.description-layout",
TextVariables.DESCRIPTION, user.getTranslation(getDescriptionReference())));
+
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
- if (score > RanksManager.BANNED_RANK && score < island.getFlag(this)) {
+
+ if (score > RanksManager.BANNED_RANK && score < y) {
pib.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + user.getTranslation(reference));
- } else if (score <= RanksManager.OWNER_RANK && score > island.getFlag(this)) {
+ } else if (score <= RanksManager.OWNER_RANK && score > y) {
pib.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + user.getTranslation(reference));
- } else if (score == island.getFlag(this)) {
+ } else if (score == y) {
pib.description(user.getTranslation("protection.panel.flag-item.minimal-rank") + user.getTranslation(reference));
}
});
}
+
return pib;
}
@@ -469,7 +477,7 @@ public class Flag implements Comparable {
public Set getSubflags() {
return subflags;
}
-
+
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.
@@ -482,7 +490,7 @@ public class Flag implements Comparable {
public boolean setTranslatedName(Locale locale, String name) {
return BentoBox.getInstance().getLocalesManager().setTranslation(locale, getNameReference(), name);
}
-
+
/**
* Set the name of this flag for a specified locale. This enables the flag's name to be assigned via API. It will not be stored anywhere
* and must be rewritten using this call every time the flag is built.
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/PanelItem.java b/src/main/java/world/bentobox/bentobox/api/panels/PanelItem.java
index 219beb636..589e936d2 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/PanelItem.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/PanelItem.java
@@ -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);
diff --git a/src/main/java/world/bentobox/bentobox/api/panels/TabbedPanel.java b/src/main/java/world/bentobox/bentobox/api/panels/TabbedPanel.java
index cc1a41290..3c93ad8a6 100644
--- a/src/main/java/world/bentobox/bentobox/api/panels/TabbedPanel.java
+++ b/src/main/java/world/bentobox/bentobox/api/panels/TabbedPanel.java
@@ -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 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 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;
}
+
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/api/user/User.java b/src/main/java/world/bentobox/bentobox/api/user/User.java
index 4e20a172c..f9508eb37 100644
--- a/src/main/java/world/bentobox/bentobox/api/user/User.java
+++ b/src/main/java/world/bentobox/bentobox/api/user/User.java
@@ -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> VALIDATION_CHECK;
static {
Map> 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);
}
@@ -153,7 +160,6 @@ public class User implements MetaDataAble {
public static void removePlayer(Player player) {
if (player != null) {
users.remove(player.getUniqueId());
- BentoBox.getInstance().getPlayers().removePlayer(player);
}
}
@@ -711,8 +717,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 +735,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 +753,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 +767,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.
diff --git a/src/main/java/world/bentobox/bentobox/commands/BentoBoxAboutCommand.java b/src/main/java/world/bentobox/bentobox/commands/BentoBoxAboutCommand.java
index 1f511565e..f2694f563 100644
--- a/src/main/java/world/bentobox/bentobox/commands/BentoBoxAboutCommand.java
+++ b/src/main/java/world/bentobox/bentobox/commands/BentoBoxAboutCommand.java
@@ -28,7 +28,7 @@ public class BentoBoxAboutCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List 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;
}
diff --git a/src/main/java/world/bentobox/bentobox/commands/BentoBoxMigrateCommand.java b/src/main/java/world/bentobox/bentobox/commands/BentoBoxMigrateCommand.java
index b4b763270..0face2d24 100644
--- a/src/main/java/world/bentobox/bentobox/commands/BentoBoxMigrateCommand.java
+++ b/src/main/java/world/bentobox/bentobox/commands/BentoBoxMigrateCommand.java
@@ -1,8 +1,13 @@
package world.bentobox.bentobox.commands;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
import java.util.Set;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
@@ -20,6 +25,8 @@ import world.bentobox.bentobox.database.objects.DataObject;
public class BentoBoxMigrateCommand extends ConfirmableCommand {
private static final String MIGRATED = "commands.bentobox.migrate.migrated";
+ private Queue> classQueue;
+ private BukkitTask task;
/**
* Reloads settings, addons and localization command
@@ -41,11 +48,21 @@ public class BentoBoxMigrateCommand extends ConfirmableCommand {
user.sendMessage("commands.bentobox.migrate.addons");
Set> classSet = getPlugin().getAddonsManager().getDataObjects();
classSet.addAll(Database.getDataobjects());
- classSet.forEach(t -> {
- user.sendMessage("commands.bentobox.migrate.class", TextVariables.DESCRIPTION, BentoBox.getInstance().getSettings().getDatabasePrefix() + t.getCanonicalName());
- new Database<>(getPlugin(), t).loadObjects();
- user.sendMessage(MIGRATED);
- });
+ // Put classSet into classQueue
+ classQueue = new LinkedList<>(classSet);
+ // Start a scheduler to step through these in a reasonable time
+ task = Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> {
+ Class extends DataObject> t = classQueue.poll();
+ if (t != null) {
+ user.sendMessage("commands.bentobox.migrate.class", TextVariables.DESCRIPTION,
+ BentoBox.getInstance().getSettings().getDatabasePrefix() + t.getCanonicalName());
+ new Database<>(getPlugin(), t).loadObjects();
+ user.sendMessage(MIGRATED);
+ } else {
+ user.sendMessage("commands.bentobox.migrate.completed");
+ task.cancel();
+ }
+ }, 0, 20L);
});
return true;
}
diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java
index 60d95c89f..7f26e3180 100644
--- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java
+++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java
@@ -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 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 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 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,31 @@ 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();
+ }
}
/**
@@ -1986,4 +2077,41 @@ public class Island implements DataObject, MetaDataAble {
+ commandRanks + ", reserved=" + reserved + ", metaData=" + metaData + ", homes=" + homes
+ ", maxHomes=" + maxHomes + "]";
}
+
+ /**
+ * @return the primaries
+ */
+ public Set getPrimaries() {
+ if (primaries == null) {
+ primaries = new HashSet<>();
+ }
+ return primaries;
+ }
+
+ /**
+ * @param primaries the primaries to set
+ */
+ public void setPrimaries(Set 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);
+ }
+
+
}
diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Players.java b/src/main/java/world/bentobox/bentobox/database/objects/Players.java
index 3cf06d2c0..ca384ed12 100644
--- a/src/main/java/world/bentobox/bentobox/database/objects/Players.java
+++ b/src/main/java/world/bentobox/bentobox/database/objects/Players.java
@@ -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 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 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 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 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
diff --git a/src/main/java/world/bentobox/bentobox/database/objects/TeamInvite.java b/src/main/java/world/bentobox/bentobox/database/objects/TeamInvite.java
new file mode 100644
index 000000000..0988a0424
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/database/objects/TeamInvite.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/database/transition/TransitionDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/transition/TransitionDatabaseHandler.java
index c1402be8a..966be709c 100644
--- a/src/main/java/world/bentobox/bentobox/database/transition/TransitionDatabaseHandler.java
+++ b/src/main/java/world/bentobox/bentobox/database/transition/TransitionDatabaseHandler.java
@@ -44,8 +44,16 @@ public class TransitionDatabaseHandler extends AbstractDatabaseHandler {
List listTo = toHandler.loadObjects();
// If source database has objects, then delete and save them in the destination database
for (T object : listFrom) {
- toHandler.saveObject(object);
- fromHandler.deleteObject(object);
+ toHandler.saveObject(object).thenAccept(b -> {
+ // Only delete if save was successful
+ if (b) {
+ try {
+ fromHandler.deleteObject(object);
+ } catch (IllegalAccessException | InvocationTargetException | IntrospectionException e) {
+ plugin.logStacktrace(e);
+ }
+ }
+ });
}
// Merge results
listTo.addAll(listFrom);
diff --git a/src/main/java/world/bentobox/bentobox/hooks/ItemsAdderHook.java b/src/main/java/world/bentobox/bentobox/hooks/ItemsAdderHook.java
index 721058303..e33eca793 100644
--- a/src/main/java/world/bentobox/bentobox/hooks/ItemsAdderHook.java
+++ b/src/main/java/world/bentobox/bentobox/hooks/ItemsAdderHook.java
@@ -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 getAllBlocksLocationsList(Chunk chunk)
+
+ @Nullable
+ public Map getAllBlocksLocations(Chunk chunk)
+
+ public void runActionOnBlocks(Chunk chunk, BiConsumer 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 getAllBlocksLocationsList(Chunk chunk) {
+ return CustomBlock.Advanced.getAllBlocksLocationsList(chunk);
+ }
+
+ @Nullable
+ public Map getAllBlocksLocations(Chunk chunk) {
+ return CustomBlock.Advanced.getAllBlocksLocations(chunk);
+ }
+
+ public void runActionOnBlocks(Chunk chunk, BiConsumer action) {
+ CustomBlock.Advanced.runActionOnBlocks(chunk, action);
+ }
+
class BlockInteractListener extends FlagListener {
/**
diff --git a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java
index 77809ce18..5a6b9ae70 100644
--- a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java
+++ b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java
@@ -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 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());
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/hooks/MultipaperHook.java b/src/main/java/world/bentobox/bentobox/hooks/MultipaperHook.java
new file mode 100644
index 000000000..dc578e5cc
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/hooks/MultipaperHook.java
@@ -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 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 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> 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> 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();
+ }
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java
index f73f3dac3..605e020c3 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java
@@ -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,7 +75,6 @@ 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);
}
@@ -110,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 -> {
@@ -181,13 +181,15 @@ public class JoinLeaveListener implements Listener {
user.getPlayer().getInventory().clear();
}
- playerData.getPendingKicks().remove(world.getName());
- players.save(user.getUniqueId());
+ Set 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
@@ -218,8 +220,7 @@ 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))
@@ -236,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());
}
}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java b/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java
index 53d402c5e..6cb57fd45 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java
@@ -61,6 +61,7 @@ public class PanelListenerManager implements Listener {
// Refresh
l.refreshPanel();
});
+
} else {
// Wrong name - delete this panel
openPanels.remove(user.getUniqueId());
diff --git a/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java b/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java
new file mode 100644
index 000000000..72e42adae
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java
@@ -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);
+ }
+ });
+
+ }
+
+
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
index 8b78b725d..d5891191b 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandCycleClick.java
@@ -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);
}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java
index 0ad6a318d..b40c99b05 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java
@@ -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
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java
index 6d61d761a..0a09e196b 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java
@@ -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 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));
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java
index 942cb7edb..3553d61f8 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java
@@ -51,7 +51,10 @@ public class HurtingListener extends FlagListener {
public void onEntityDamage(final EntityDamageByEntityEvent e)
{
// Mobs being hurt
- if (Util.isPassiveEntity(e.getEntity()))
+ if (Util.isTamableEntity(e.getEntity())) {
+ this.respond(e, e.getDamager(), Flags.HURT_TAMED_ANIMALS);
+ }
+ else if (Util.isPassiveEntity(e.getEntity()))
{
this.respond(e, e.getDamager(), Flags.HURT_ANIMALS);
}
@@ -92,7 +95,8 @@ public class HurtingListener extends FlagListener {
return;
}
- if ((Util.isPassiveEntity(e.getCaught()) && checkIsland(e, e.getPlayer(), e.getCaught().getLocation(), Flags.HURT_ANIMALS))
+ if ((Util.isTamableEntity(e.getCaught()) && checkIsland(e, e.getPlayer(), e.getCaught().getLocation(), Flags.HURT_TAMED_ANIMALS))
+ || (Util.isPassiveEntity(e.getCaught()) && checkIsland(e, e.getPlayer(), e.getCaught().getLocation(), Flags.HURT_ANIMALS))
|| (Util.isHostileEntity(e.getCaught()) && checkIsland(e, e.getPlayer(), e.getCaught().getLocation(), Flags.HURT_MONSTERS))
|| (e.getCaught() instanceof AbstractVillager && checkIsland(e, e.getPlayer(), e.getCaught().getLocation(), Flags.HURT_VILLAGERS))) {
e.getHook().remove();
@@ -113,7 +117,11 @@ public class HurtingListener extends FlagListener {
if (e.getRightClicked() instanceof Parrot
&& (e.getHand().equals(EquipmentSlot.HAND) && e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.COOKIE))
|| (e.getHand().equals(EquipmentSlot.OFF_HAND) && e.getPlayer().getInventory().getItemInOffHand().getType().equals(Material.COOKIE))) {
- checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.HURT_ANIMALS);
+ if (((Parrot) e.getRightClicked()).isTamed()) {
+ checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.HURT_TAMED_ANIMALS);
+ } else {
+ checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.HURT_ANIMALS);
+ }
}
}
@@ -139,6 +147,13 @@ public class HurtingListener extends FlagListener {
}
}
+ // Tamed animals being hurt
+ if (Util.isTamableEntity(entity) && !checkIsland(e, attacker, entity.getLocation(), Flags.HURT_TAMED_ANIMALS)) {
+ for (PotionEffect effect : e.getPotion().getEffects()) {
+ entity.removePotionEffect(effect.getType());
+ }
+ }
+
// Mobs being hurt
if (Util.isPassiveEntity(entity) && !checkIsland(e, attacker, entity.getLocation(), Flags.HURT_ANIMALS)) {
for (PotionEffect effect : e.getPotion().getEffects()) {
@@ -189,6 +204,10 @@ public class HurtingListener extends FlagListener {
if (Util.isHostileEntity(entity)) {
checkIsland(e, attacker, entity.getLocation(), Flags.HURT_MONSTERS);
}
+ // Tamed animals being hurt
+ if (Util.isTamableEntity(entity)) {
+ checkIsland(e, attacker, entity.getLocation(), Flags.HURT_TAMED_ANIMALS);
+ }
// Mobs being hurt
if (Util.isPassiveEntity(entity)) {
checkIsland(e, attacker, entity.getLocation(), Flags.HURT_ANIMALS);
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LeashListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LeashListener.java
index c07366d56..86724f379 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LeashListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LeashListener.java
@@ -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);
}
}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PaperShearingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PaperShearingListener.java
new file mode 100644
index 000000000..24dbd31c6
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PaperShearingListener.java
@@ -0,0 +1,18 @@
+package world.bentobox.bentobox.listeners.flags.protection;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import io.papermc.paper.event.block.PlayerShearBlockEvent;
+import world.bentobox.bentobox.api.flags.FlagListener;
+import world.bentobox.bentobox.lists.Flags;
+
+public class PaperShearingListener extends FlagListener {
+
+ // Block shearing - paper only
+ @EventHandler(priority = EventPriority.LOW)
+ public void onShearBlock(final PlayerShearBlockEvent e) {
+ checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.SHEARING);
+ }
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ShearingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ShearingListener.java
index e35c370ee..028cff069 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ShearingListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ShearingListener.java
@@ -1,11 +1,13 @@
package world.bentobox.bentobox.listeners.flags.protection;
+import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerShearEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
+import world.bentobox.bentobox.util.Util;
/**
* Handles shearing
@@ -14,6 +16,12 @@ import world.bentobox.bentobox.lists.Flags;
*/
public class ShearingListener extends FlagListener {
+ public ShearingListener() {
+ if (Util.isPaper()) {
+ Bukkit.getPluginManager().registerEvents(new PaperShearingListener(), getPlugin());
+ }
+ }
+
// Protect sheep
@EventHandler(priority = EventPriority.LOW)
public void onShear(final PlayerShearEntityEvent e) {
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java
index 1543de382..f0878c8ce 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java
@@ -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 TNT_TYPES = List.of(EntityType.PRIMED_TNT, EntityType.MINECART_TNT);
+ private static final List TNT_TYPES = List.of(
+ findFirstMatchingEnum(EntityType.class, "PRIMED_TNT", "TNT"),
+ findFirstMatchingEnum(EntityType.class, "MINECART_TNT", "TNT_MINECART"));
+ private static > T findFirstMatchingEnum(Class enumClass, String... values) {
+ if (enumClass == null || values == null) {
+ return null;
+ }
+ for (String value : values) {
+ Optional 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
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java
index 2b6689165..401295fad 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java
@@ -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);
}
}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java
index bbe835457..7adf0d6d6 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java
@@ -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);
+ }
+ }
+ }
}
diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java
index 0900d396a..25ac4051a 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java
@@ -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;
diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java
index 9fc685b24..33d4bbf16 100644
--- a/src/main/java/world/bentobox/bentobox/lists/Flags.java
+++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java
@@ -283,6 +283,7 @@ public final class Flags {
public static final Flag HURT_ANIMALS = new Flag.Builder("HURT_ANIMALS", Material.STONE_SWORD).listener(new HurtingListener()).mode(Flag.Mode.ADVANCED).build();
public static final Flag HURT_MONSTERS = new Flag.Builder("HURT_MONSTERS", Material.WOODEN_SWORD).mode(Flag.Mode.BASIC).build();
public static final Flag HURT_VILLAGERS = new Flag.Builder("HURT_VILLAGERS", Material.GOLDEN_SWORD).mode(Flag.Mode.ADVANCED).build();
+ public static final Flag HURT_TAMED_ANIMALS = new Flag.Builder("HURT_TAMABLE_ENTITIES", Material.DIAMOND_SWORD).mode(Flag.Mode.ADVANCED).build();
// Leashes
public static final Flag LEASH = new Flag.Builder("LEASH", Material.LEAD).listener(new LeashListener()).build();
diff --git a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
index e03b2eb81..d472ba78d 100644
--- a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
+++ b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java
@@ -295,6 +295,14 @@ public enum GameModePlaceholder {
RANK("rank",
(addon, user, island) -> (island == null || user == null) ? ""
: user.getTranslation(RanksManager.getInstance().getRank(island.getRank(user)))),
+ /**
+ * Returns the rank this player has on this island.
+ * @since 2.4.0
+ */
+ VISITED_ISLAND_RANK("visited_island_rank",
+ (addon, user, island) -> getVisitedIsland(addon, user)
+ .map(is -> user.getTranslation(RanksManager.getInstance().getRank(is.getRank(user)))).orElse("")),
+
/**
* Returns how many times this player reset his island.
* @since 1.5.0
diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
index 486edb811..7d7364365 100644
--- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -15,10 +14,10 @@ import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
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 spawns = new ConcurrentHashMap<>();
+
+ private Map last = new ConcurrentHashMap<>();
@NonNull
- private Database handler;
-
- /**
- * The last locations where an island were put. This is not stored persistently
- * and resets when the server starts
- */
- private final Map last;
+ private static Database handler;
/**
* Island Cache
*/
@NonNull
private IslandCache islandCache;
- // Quarantined islands
- @NonNull
- private final Map> quarantineCache;
// Deleted islands
@NonNull
private final List 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 handler) {
- this.handler = handler;
+ public void setHandler(@NonNull Database 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
+ saveIsland(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.
*
@@ -337,6 +374,17 @@ public class IslandsManager {
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 getIslands(UUID uniqueId) {
+ return islandCache.getIslands(uniqueId);
+ }
+
/**
* Gets all the islands for this player in this world that this player owns.
*
@@ -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 getIslands() {
- return islandCache.getIslands();
+ return handler.loadObjects().stream().toList();
}
/**
@@ -417,7 +465,17 @@ public class IslandsManager {
*/
@NonNull
public Collection getIslands(@NonNull World world) {
- return islandCache.getIslands(world);
+ return handler.loadObjects().stream().filter(i -> world.equals(i.getWorld())).toList();
+ }
+
+ /**
+ * Return island with uniqueId. Loads from database. Will block, so be careful.
+ * @param uniqueID id of island
+ * @return Optional Island object
+ * @since 2.4.0
+ */
+ public Optional loadIsland(String uniqueID) {
+ return Optional.ofNullable(handler.loadObject(uniqueID));
}
/**
@@ -462,7 +520,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 +544,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 +565,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 +607,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 +799,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 +954,7 @@ public class IslandsManager {
*/
@NonNull
public Optional getSpawn(@NonNull World world) {
- return Optional.ofNullable(spawn.get(world));
+ return Optional.ofNullable(spawns.get(world));
}
/**
@@ -901,7 +965,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 +1196,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 +1208,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 +1223,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 +1249,6 @@ public class IslandsManager {
*/
public void load() throws IOException {
islandCache.clear();
- quarantineCache.clear();
List toQuarantine = new ArrayList<>();
int owned = 0;
int unowned = 0;
@@ -1206,9 +1262,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 +1272,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, true);
+
+ if (island.isSpawn()) {
// Success, set spawn if this is the spawn island.
this.setSpawn(island);
} else {
@@ -1394,15 +1438,10 @@ public class IslandsManager {
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!"));
+
}
});
}
@@ -1419,17 +1458,17 @@ public class IslandsManager {
}
/**
- * Save the all the islands to the database
+ * Save the all the cached islands to the database
*
* @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 (Island island : islandCache.getIslands()) {
+ for (Island island : islandCache.getCachedIslands()) {
if (island.isChanged()) {
try {
- handler.saveObjectAsync(island);
+ saveIsland(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
@@ -1439,7 +1478,7 @@ public class IslandsManager {
}
isSaveTaskRunning = true;
- Queue queue = new LinkedList<>(islandCache.getIslands());
+ Queue queue = new LinkedList<>(islandCache.getCachedIslands());
new BukkitRunnable() {
@Override
public void run() {
@@ -1452,7 +1491,7 @@ public class IslandsManager {
}
if (island.isChanged()) {
try {
- handler.saveObjectAsync(island);
+ saveIsland(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
@@ -1473,9 +1512,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);
}
@@ -1483,7 +1526,7 @@ public class IslandsManager {
public void shutdown() {
plugin.log("Removing coops from islands...");
// Remove all coop associations
- islandCache.getIslands().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK));
+ islandCache.getCachedIslands().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK));
plugin.log("Saving islands - this has to be done sync so it may take a while with a lot of islands...");
saveAll();
plugin.log("Islands saved.");
@@ -1495,11 +1538,11 @@ public class IslandsManager {
/**
* 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
* @return true if in team, false if not
- * @see Consider checking the island itself {@link Island#inTeam(UUID)}
*/
public boolean inTeam(World world, @NonNull UUID playerUUID) {
return this.islandCache.getIslands(world, playerUUID).stream()
@@ -1603,23 +1646,37 @@ public class IslandsManager {
* @param uniqueId - UUID of player
*/
public void clearRank(int rank, UUID uniqueId) {
- islandCache.getIslands().forEach(
+ islandCache.getCachedIslands().forEach(
i -> i.getMembers().entrySet().removeIf(e -> e.getKey().equals(uniqueId) && e.getValue() == rank));
}
/**
- * 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) {
+ // When mocking, handler can be null so this null check avoids errors
+ if (handler != null && handler.objectExists(island.getUniqueId())) {
+ island.clearChanged();
+ saveIsland(island).thenAccept(b -> MultiLib.notify("bentobox-updateIsland", island.getUniqueId()));
+ }
+ }
+
+ /**
+ * Saves the island async to the database
+ * @param island Island object to be saved
+ * @return CompletableFuture when done
+ * @since 2.4.0
+ */
+ public static CompletableFuture saveIsland(Island island) {
+ return handler.saveObjectAsync(island);
}
/**
* Try to get an island by its unique id
*
- * @param uniqueId - unique id string
+ * @param uniqueId - unique id of island
* @return optional island
* @since 1.3.0
*/
@@ -1629,105 +1686,26 @@ public class IslandsManager {
}
/**
- * 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 null = unowned islands
- * @return list of islands; may be empty
- * @since 1.3.0
+ * Try to get an island by its unique id. If you are needing to load all the islands to check something
+ * but do not need to have them cached, then use this method and set cache to false.
+ *
+ * @param uniqueId - unique id of island
+ * @param cache - if false, island will not be cached if it is not already
+ * @return optional island
+ * @since 2.4.0
*/
@NonNull
- public List getQuarantinedIslandByUser(@NonNull World world, @Nullable UUID uuid) {
- return quarantineCache.getOrDefault(uuid, Collections.emptyList()).stream()
- .filter(i -> i.getWorld().equals(world)).toList();
+ public Optional getIslandById(String uniqueId, boolean cache) {
+ return Optional.ofNullable(islandCache.getIslandById(uniqueId, cache));
}
/**
- * Delete quarantined islands owned by uuid in this world
- *
- * @param world - world
- * @param uuid - target player's UUID, or null = unowned islands
- * @since 1.3.0
+ * Returns if this is a known island uniqueId. Will not load the island from the database if it is not loaded already.
+ * @param uniqueId - unique id of island
+ * @return true if this island exists
*/
- 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> 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 true if successful, otherwise false
- * @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;
+ public boolean isIslandId(String uniqueId) {
+ return islandCache.isIslandId(uniqueId);
}
/**
@@ -1753,121 +1731,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 checkTeams(User user, World world) {
- CompletableFuture r = new CompletableFuture<>();
- user.sendMessage("commands.admin.team.fix.scanning");
- Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
- Map owners = new HashMap<>();
- Map freq = new HashMap<>();
- Map> 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?
*
@@ -1899,14 +1762,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);
}
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java
index 007d67ba1..957f1a37c 100644
--- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java
@@ -2,23 +2,21 @@ package world.bentobox.bentobox.managers;
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 java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
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 +29,9 @@ public class PlayersManager {
private final BentoBox plugin;
private Database handler;
private final Database names;
+ private final Map playerCache = new ConcurrentHashMap<>();
- private final Map playerCache;
- private final Set inTeleport;
-
- private boolean isSaveTaskRunning;
+ private final Set inTeleport; // this needs databasing
/**
* Provides a memory cache of online player information
@@ -50,7 +46,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 +57,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 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 +68,38 @@ 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
+ Players player = loadPlayer(playerUUID);
+ if (player != null) {
+ return player;
}
- return playerCache.get(uuid);
+ Players newPlayer = new Players(plugin, playerUUID);
+ handler.saveObjectAsync(newPlayer);
+ return newPlayer;
+ }
+
+ /**
+ * Force load the player from the database. The player must be known to BenoBox. If it is not
+ * use {@link #addPlayer(UUID)} instead. This is a blocking call, so be careful.
+ * @param uuid UUID of player
+ * @return Players object representing that player
+ * @since 2.4.0
+ */
+ public @Nullable Players loadPlayer(UUID uuid) {
+ return handler.loadObject(uuid.toString());
}
/**
@@ -146,37 +109,7 @@ public class PlayersManager {
*/
@NonNull
public Collection 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 +120,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 +139,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 +148,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 +168,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 +179,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 +191,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 +207,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 +218,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 +227,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 +238,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 +250,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 +262,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 +289,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 +314,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 +390,21 @@ public class PlayersManager {
// Player total XP (not displayed)
target.getPlayer().setTotalExperience(0);
}
- // Save player
- save(target.getUniqueId());
+ }
+
+ /**
+ * Saves the player async to the database. The player has to be known to BentoBox to be saved.
+ * Players are usually detected by BentoBox when they join the server, so this is not an issue.
+ * @param uuid UUID of the player
+ * @return Completable future true when done, or false if not saved for some reason, e.g., invalid UUID
+ * @since 2.4.0
+ */
+ public CompletableFuture savePlayer(UUID uuid) {
+ Players p = this.getPlayer(uuid);
+ if (p != null) {
+ return handler.saveObjectAsync(p);
+ }
+ return CompletableFuture.completedFuture(false);
}
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java
index a72560ded..acfdabc8f 100644
--- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java
+++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java
@@ -9,10 +9,13 @@ 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;
@@ -20,6 +23,7 @@ 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;
@@ -30,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
*/
@@ -42,39 +44,85 @@ public class IslandCache {
* UUID, value is a set of islands
*/
@NonNull
- private final Map<@NonNull UUID, Set> islandsByUUID;
+ private final Map<@NonNull UUID, Set> islandsByUUID;
@NonNull
private final Map<@NonNull World, @NonNull IslandGrid> grids;
+ private final @NonNull Database handler;
- public IslandCache() {
- islandsByLocation = new HashMap<>();
+ public IslandCache(@NonNull Database 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 newMembers = newIsland.getMembers().keySet();
+ if (oldIsland != null) {
+ Set 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 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
+ * Caches island.
*
* @param island island to add, not null
* @return true if successfully added, false if not
*/
public boolean addIsland(@NonNull Island island) {
+ return addIsland(island, false);
+ }
+
+ /**
+ * Adds an island to the grid, used for new islands
+ *
+ * @param island island to add, not null
+ * @param noCache - if true, island will not be cached
+ * @return true if successfully added, false if not
+ */
+ public boolean addIsland(@NonNull Island island, boolean noCache) {
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(), noCache ? null : island);
// 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;
@@ -83,14 +131,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());
}
/**
@@ -100,11 +148,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();
}
@@ -113,29 +160,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 set : islandsByUUID.values()) {
- Iterator is = set.iterator();
- while (is.hasNext()) {
- Island i = is.next();
- if (i.equals(island)) {
- is.remove();
- }
- }
- // set.removeIf(island::equals);
+ for (Set set : islandsByUUID.values()) {
+ set.removeIf(island.getUniqueId()::equals);
}
}
@@ -145,24 +182,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 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
@@ -172,19 +196,19 @@ public class IslandCache {
* @return island or null if none
*/
@Nullable
- public Island get(@NonNull World world, @NonNull UUID uuid) {
+ public Island getIsland(@NonNull World world, @NonNull UUID uuid) {
List 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;
}
@@ -200,7 +224,8 @@ public class IslandCache {
if (w == null) {
return new ArrayList<>();
}
- return islandsByUUID.computeIfAbsent(uuid, k -> new HashSet<>()).stream().filter(island -> w.equals(island.getWorld()))
+ 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());
}
@@ -212,8 +237,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);
+ }
}
}
@@ -235,18 +268,51 @@ public class IslandCache {
/**
* Returns an unmodifiable collection 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. This is a very heavy operation likely to cause lag.
*
* @return unmodifiable collection containing every island.
*/
@NonNull
public Collection getIslands() {
- return Collections.unmodifiableCollection(islandsByLocation.values());
+ List result = new ArrayList<>();
+ for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
+ Island island = entry.getValue() != null ? entry.getValue() : loadIsland(entry.getKey());
+ if (island != null) {
+ result.add(island);
+ }
+ }
+
+ return Collections.unmodifiableCollection(result);
+ }
+
+ /**
+ * Loads the island with the uniqueId from the database. Note, this could be a blocking call
+ * and lag the server, so be careful using it.
+ * @param uniqueId unique ID of the island
+ * @return Island or null if that uniqueID is unknown
+ * @since 2.4.0
+ */
+ public Island loadIsland(String uniqueId) {
+ return handler.loadObject(uniqueId);
}
/**
* Returns an unmodifiable collection 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 getCachedIslands() {
+ return islandsById.entrySet().stream().filter(en -> Objects.nonNull(en.getValue())).map(Map.Entry::getValue)
+ .toList();
+ }
+
+ /**
+ * Returns an unmodifiable collection 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
@@ -259,9 +325,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 result = new ArrayList<>();
+ for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
+ Island island = entry.getValue() != null ? entry.getValue() : loadIsland(entry.getKey());
+ if (island != null && overworld.equals(island.getWorld())) {
+ result.add(island);
+ }
+ }
+
+ return Collections.unmodifiableCollection(result);
}
/**
@@ -276,7 +349,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()));
}
@@ -286,20 +360,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 removePlayer(@NonNull World world, @NonNull UUID uuid) {
- World w = Util.getWorld(world);
- Set 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 playerIslandIds = islandsByUUID.get(uuid);
+ Set 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 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 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();
@@ -307,11 +385,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;
}
/**
@@ -321,11 +401,12 @@ public class IslandCache {
* @param uuid uuid of member to remove
*/
public void removePlayer(@NonNull Island island, @NonNull UUID uuid) {
- Set islandSet = islandsByUUID.get(uuid);
+ Set islandSet = islandsByUUID.get(uuid);
if (islandSet != null) {
- islandSet.remove(island);
+ islandSet.remove(island.getUniqueId());
}
island.removeMember(uuid);
+ island.removePrimary(uuid);
}
/**
@@ -334,17 +415,18 @@ 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();
+ // Get from grids because this is where we have islands by world
+ return this.grids.containsKey(world) ? this.grids.get(world).getSize() : 0L;
}
/**
@@ -356,11 +438,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());
}
island.setRank(newOwnerUUID, RanksManager.OWNER_RANK);
- islandsByLocation.put(island.getCenter(), island);
- islandsById.put(island.getUniqueId(), island);
+ setIslandById(island);
}
/**
@@ -372,42 +453,50 @@ public class IslandCache {
*/
@Nullable
public Island getIslandById(@NonNull String uniqueId) {
- return islandsById.get(uniqueId);
+ // Load from cache or database
+ return getIslandById(uniqueId, true);
}
/**
- * Removes an island from the cache completely without altering the island
- * object
+ * Get the island by unique id
*
- * @param island - island to remove
- * @since 1.3.0
+ * @param uniqueId unique id of the Island.
+ * @param cache if true, then the Island will be cached if it is not already
+ * @return island or null if none found
+ * @since 2.4.0
*/
- 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;
+ @Nullable
+ public Island getIslandById(@NonNull String uniqueId, boolean cache) {
+ Island island = islandsById.get(uniqueId);
+ if (island != null) {
+ return island;
}
- if (grids.containsKey(w)) {
- grids.get(w).removeFromGrid(island);
+ island = loadIsland(uniqueId);
+ if (cache && island != null) {
+ islandsById.put(uniqueId, island);
}
+ return island;
}
/**
- * Resets all islands in this game mode to default flag settings
+ * 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
+ */
+ Island setIslandById(Island island) {
+ return islandsById.put(island.getUniqueId().intern(), island);
+ }
+
+ /**
+ * 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));
}
/**
@@ -418,13 +507,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));
}
/**
@@ -437,4 +522,22 @@ 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 getIslands(UUID uniqueId) {
+ return islandsByUUID.getOrDefault(uniqueId, Collections.emptySet()).stream().map(this::getIslandById).toList();
+ }
+
+ /**
+ * Returns if this is a known island uniqueId. Will not load the island from the database if it is not loaded already.
+ * @param uniqueId - unique id of island
+ * @return true if this island exists
+ */
+ public boolean isIslandId(String uniqueId) {
+ return this.islandsById.containsKey(uniqueId);
+ }
+
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java
index db404f1a3..0893a7f09 100644
--- a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java
+++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java
@@ -3,7 +3,6 @@ package world.bentobox.bentobox.managers.island;
import java.util.Map.Entry;
import java.util.TreeMap;
-import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
/**
@@ -12,8 +11,16 @@ import world.bentobox.bentobox.database.objects.Island;
*
*/
class IslandGrid {
- private final TreeMap> grid = new TreeMap<>();
- private final BentoBox plugin = BentoBox.getInstance();
+ private final TreeMap> grid = new TreeMap<>();
+ private final IslandCache im;
+
+ /**
+ * @param im IslandsManager
+ */
+ public IslandGrid(IslandCache im) {
+ super();
+ this.im = im;
+ }
/**
* Adds island to grid
@@ -21,50 +28,23 @@ class IslandGrid {
* @return true if successfully added, false if island already exists, or there is an overlap
*/
public boolean addToGrid(Island island) {
+ // Check if we know about this island already
if (grid.containsKey(island.getMinX())) {
- TreeMap zEntry = grid.get(island.getMinX());
+ TreeMap zEntry = grid.get(island.getMinX());
if (zEntry.containsKey(island.getMinZ())) {
- // There is an overlap or duplicate
- plugin.logError("Cannot load island. Overlapping: " + island.getUniqueId());
- plugin.logError("Location: " + island.getCenter());
- // Get the previously loaded island
- Island firstLoaded = zEntry.get(island.getMinZ());
- if (firstLoaded.getOwner() == null && island.getOwner() != null) {
- // This looks fishy. We prefer to load islands that have an owner. Swap the two
- plugin.logError("Duplicate island has an owner, so using that one. " + island.getOwner());
- firstLoaded = new Island(island);
- zEntry.put(island.getMinZ(), firstLoaded);
- } else if (firstLoaded.getOwner() != null && island.getOwner() != null) {
- // Check if the owners are the same - this is a true duplicate
- if (firstLoaded.getOwner().equals(island.getOwner())) {
- // Find out which one is the original
- if (firstLoaded.getCreatedDate() > island.getCreatedDate()) {
- plugin.logError("Same owner duplicate. Swapping based on creation date.");
- // FirstLoaded is the newer
- firstLoaded = new Island(island);
- zEntry.put(island.getMinZ(), firstLoaded);
- } else {
- plugin.logError("Same owner duplicate.");
- }
- } else {
- plugin.logError("Duplicate but different owner. Keeping first loaded.");
- plugin.logError("This is serious!");
- plugin.logError("1st loaded ID: " + firstLoaded.getUniqueId());
- plugin.logError("1st loaded owner: " + firstLoaded.getOwner());
- plugin.logError("2nd loaded ID: " + island.getUniqueId());
- plugin.logError("2nd loaded owner: " + island.getOwner());
- }
+ if (island.getUniqueId().equals(zEntry.get(island.getMinZ()))) {
+ return true;
}
return false;
} else {
// Add island
- zEntry.put(island.getMinZ(), island);
+ zEntry.put(island.getMinZ(), island.getUniqueId());
grid.put(island.getMinX(), zEntry);
}
} else {
// Add island
- TreeMap zEntry = new TreeMap<>();
- zEntry.put(island.getMinZ(), island);
+ TreeMap zEntry = new TreeMap<>();
+ zEntry.put(island.getMinZ(), island.getUniqueId());
grid.put(island.getMinX(), zEntry);
}
return true;
@@ -76,43 +56,59 @@ class IslandGrid {
* @return true if island existed and was deleted, false if there was nothing to delete
*/
public boolean removeFromGrid(Island island) {
- // Remove from grid
- if (island != null) {
- int x = island.getMinX();
- int z = island.getMinZ();
- if (grid.containsKey(x)) {
- TreeMap zEntry = grid.get(x);
- if (zEntry.containsKey(z)) {
- // Island exists - delete it
- zEntry.remove(z);
- grid.put(x, zEntry);
- return true;
- }
- }
+ String id = island.getUniqueId();
+ boolean removed = grid.values().stream()
+ .anyMatch(innerMap -> innerMap.values().removeIf(innerValue -> innerValue.equals(id)));
+
+ grid.values().removeIf(TreeMap::isEmpty);
+
+ return removed;
+ }
+
+ /**
+ * Retrieves the island located at the specified x and z coordinates, covering both the protected area
+ * and the full island space. Returns null if no island exists at the given location.
+ *
+ * @param x the x coordinate of the location
+ * @param z the z coordinate of the location
+ * @return the Island at the specified location, or null if no island is found
+ */
+ public Island getIslandAt(int x, int z) {
+ // Attempt to find the closest x-coordinate entry that does not exceed 'x'
+ Entry> xEntry = grid.floorEntry(x);
+ if (xEntry == null) {
+ return null; // No x-coordinate entry found, return null
}
- return false;
+
+ // Attempt to find the closest z-coordinate entry that does not exceed 'z' within the found x-coordinate
+ Entry zEntry = xEntry.getValue().floorEntry(z);
+ if (zEntry == null) {
+ return null; // No z-coordinate entry found, return null
+ }
+
+ // Retrieve the island using the id found in the z-coordinate entry
+ Island island = im.getIslandById(zEntry.getValue());
+ if (island == null) {
+ return null; // No island found by the id, return null
+ }
+ // Check if the specified coordinates are within the island space
+ if (island.inIslandSpace(x, z)) {
+ return island; // Coordinates are within island space, return the island
+ }
+
+ // Coordinates are outside the island space, return null
+ return null;
}
/**
- * Returns the island at the x,z location or null if there is none.
- * This includes the full island space, not just the protected area.
- *
- * @param x - x coordinate
- * @param z - z coordinate
- * @return Island or null
+ * @return number of islands stored in the grid
*/
- public Island getIslandAt(int x, int z) {
- Entry> en = grid.floorEntry(x);
- if (en != null) {
- Entry ent = en.getValue().floorEntry(z);
- if (ent != null) {
- // Check if in the island range
- Island island = ent.getValue();
- if (island.inIslandSpace(x, z)) {
- return island;
- }
- }
+ public long getSize() {
+ long count = 0;
+ for (TreeMap innerMap : grid.values()) {
+ count += innerMap.size();
}
- return null;
+ return count;
}
+
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
index d9a73819f..bb4f2cb22 100644
--- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
+++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
@@ -19,6 +19,7 @@ import world.bentobox.bentobox.api.events.island.IslandResetEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
+import world.bentobox.bentobox.managers.IslandsManager;
/**
* Create and paste a new island
@@ -217,7 +218,7 @@ public class NewIsland {
// Register metrics
plugin.getMetrics().ifPresent(BStats::increaseIslandsCreatedCount);
// Save island
- plugin.getIslands().save(island);
+ IslandsManager.updateIsland(island);
}
/**
@@ -266,8 +267,6 @@ public class NewIsland {
plugin.getIWM().getAddon(island.getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("")
+ "island.range",
island.getProtectionRange()));
- // Save the player so that if the server crashes weird things won't happen
- plugin.getPlayers().save(user.getUniqueId());
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
index 086a10226..c3ac17183 100644
--- a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
+++ b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
@@ -8,6 +8,8 @@ import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import javax.annotation.Nonnull;
+
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
@@ -33,11 +35,13 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.material.Colorable;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.BoundingBox;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
@@ -97,7 +101,10 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
final int x = chunkX;
final int z = chunkZ;
- newTasks.add(regenerateChunk(di, world, x, z));
+ // Only add chunks that are generated
+ if (world.getChunkAt(x, z, false).isGenerated()) {
+ newTasks.add(regenerateChunk(di, world, x, z));
+ }
chunkZ++;
if (chunkZ > di.getMaxZChunk()) {
chunkZ = di.getMinZChunk();
@@ -119,7 +126,13 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
return regenerateChunk(null, chunk.getWorld(), chunk.getX(), chunk.getZ());
}
- private CompletableFuture regenerateChunk(@Nullable IslandDeletion di, World world, int chunkX, int chunkZ) {
+ private CompletableFuture regenerateChunk(@Nullable IslandDeletion di, @NonNull World world, int chunkX,
+ int chunkZ) {
+
+ // Check if chunk has been generated
+ if (!world.getChunkAt(chunkX, chunkZ, false).isGenerated()) {
+ return CompletableFuture.completedFuture(null);
+ }
CompletableFuture seedWorldFuture = getSeedWorldChunk(world, chunkX, chunkZ);
@@ -201,10 +214,12 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
// Delete any 3rd party blocks
Location loc = new Location(toChunk.getWorld(), baseX + x, y, baseZ + z);
slimefunHook.ifPresent(hook -> hook.clearBlockInfo(loc, true));
- itemsAdderHook.ifPresent(hook -> hook.clearBlockInfo(loc));
+
}
}
}
+ // Items Adder
+ itemsAdderHook.ifPresent(hook -> ItemsAdderHook.deleteAllCustomBlocksInChunk(toChunk));
// Entities
Arrays.stream(fromChunk.getEntities()).forEach(e -> processEntity(e, e.getLocation().toVector().toLocation(toChunk.getWorld())));
@@ -333,7 +348,8 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
@SuppressWarnings("deprecation")
- private CompletableFuture regenerateChunk(GameModeAddon gm, IslandDeletion di, World world, int chunkX, int chunkZ) {
+ private CompletableFuture regenerateChunk(GameModeAddon gm, IslandDeletion di, @Nonnull World world,
+ int chunkX, int chunkZ) {
CompletableFuture chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ);
CompletableFuture invFuture = chunkFuture.thenAccept(chunk ->
Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance)
@@ -370,6 +386,7 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
double baseZ = chunk.getZ() << 4;
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
+ Optional slimefunHook = plugin.getHooks().getHook("Slimefun");
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (!limitBox.contains(baseX + x, 0, baseZ + z)) {
@@ -383,12 +400,11 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
}
// Delete any 3rd party blocks
Location loc = new Location(chunk.getWorld(), baseX + x, y, baseZ + z);
- plugin.getHooks().getHook("Slimefun")
- .ifPresent(sf -> ((SlimefunHook) sf).clearBlockInfo(loc, true));
- plugin.getHooks().getHook("ItemsAdder")
- .ifPresent(hook -> ((ItemsAdderHook) hook).clearBlockInfo(loc));
+ slimefunHook.ifPresent(sf -> ((SlimefunHook) sf).clearBlockInfo(loc, true));
}
}
}
+ // Items Adder
+ plugin.getHooks().getHook("ItemsAdder").ifPresent(hook -> ItemsAdderHook.deleteAllCustomBlocksInChunk(chunk));
}
}
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R1/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java
similarity index 97%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R1/PasteHandlerImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java
index 449c3b788..d9cc8f9f2 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R1/PasteHandlerImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java
@@ -1,4 +1,4 @@
-package world.bentobox.bentobox.nms.v1_20_R1;
+package world.bentobox.bentobox.nms.v1_20_0_R0_1_SNAPSHOT;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R1/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
similarity index 94%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R1/WorldRegeneratorImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
index 59a9e5d53..6b85d7272 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R1/WorldRegeneratorImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
@@ -1,4 +1,4 @@
-package world.bentobox.bentobox.nms.v1_20_R1;
+package world.bentobox.bentobox.nms.v1_20_0_R0_1_SNAPSHOT;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R2/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java
similarity index 92%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R2/PasteHandlerImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java
index 9d7f3c5fc..483025c52 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R2/PasteHandlerImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java
@@ -1,12 +1,12 @@
-package world.bentobox.bentobox.nms.v1_20_R2;
+package world.bentobox.bentobox.nms.v1_20_1_R0_1_SNAPSHOT;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
-import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
-import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.block.state.IBlockData;
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
new file mode 100644
index 000000000..d15d4809b
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
@@ -0,0 +1,26 @@
+package world.bentobox.bentobox.nms.v1_20_1_R0_1_SNAPSHOT;
+
+import org.bukkit.block.data.BlockData;
+import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
+
+import net.minecraft.core.BlockPosition;
+import net.minecraft.world.level.World;
+import net.minecraft.world.level.chunk.Chunk;
+import world.bentobox.bentobox.nms.CopyWorldRegenerator;
+
+public class WorldRegeneratorImpl extends CopyWorldRegenerator {
+
+ @Override
+ public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData,
+ boolean applyPhysics) {
+ CraftBlockData craft = (CraftBlockData) blockData;
+ World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle();
+ Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ());
+ BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z);
+ // Setting the block to air before setting to another state prevents some console errors
+ nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics);
+ nmsChunk.a(bp, craft.getState(), applyPhysics);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R3/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java
similarity index 97%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R3/PasteHandlerImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java
index e06b032fa..af6a83d7a 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R3/PasteHandlerImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java
@@ -1,4 +1,4 @@
-package world.bentobox.bentobox.nms.v1_20_R3;
+package world.bentobox.bentobox.nms.v1_20_4_R0_1_SNAPSHOT;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R3/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
similarity index 94%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R3/WorldRegeneratorImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
index 90592f736..f259a92bb 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R3/WorldRegeneratorImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
@@ -1,4 +1,4 @@
-package world.bentobox.bentobox.nms.v1_20_R3;
+package world.bentobox.bentobox.nms.v1_20_4_R0_1_SNAPSHOT;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java
new file mode 100644
index 000000000..49efdc370
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java
@@ -0,0 +1,52 @@
+package world.bentobox.bentobox.nms.v1_20_6_R0_1_SNAPSHOT;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
+
+import net.minecraft.core.BlockPosition;
+import net.minecraft.world.level.block.state.IBlockData;
+import net.minecraft.world.level.chunk.Chunk;
+import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.nms.PasteHandler;
+import world.bentobox.bentobox.util.DefaultPasteUtil;
+import world.bentobox.bentobox.util.Util;
+
+public class PasteHandlerImpl implements PasteHandler {
+
+ protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState();
+
+ /**
+ * Set the block to the location
+ *
+ * @param island - island
+ * @param location - location
+ * @param bpBlock - blueprint block
+ */
+ @Override
+ public CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) {
+ return Util.getChunkAtAsync(location).thenRun(() -> {
+ Block block = location.getBlock();
+ // Set the block data - default is AIR
+ BlockData bd = DefaultPasteUtil.createBlockData(bpBlock);
+ CraftBlockData craft = (CraftBlockData) bd;
+ net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle();
+ Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4);
+ BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
+ // Setting the block to air before setting to another state prevents some console errors
+ nmsChunk.a(bp, AIR, false);
+ nmsChunk.a(bp, craft.getState(), false);
+ block.setBlockData(bd, false);
+ DefaultPasteUtil.setBlockState(island, block, bpBlock);
+ // Set biome
+ if (bpBlock.getBiome() != null) {
+ block.setBiome(bpBlock.getBiome());
+ }
+ });
+ }
+}
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
new file mode 100644
index 000000000..fbff31665
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
@@ -0,0 +1,26 @@
+package world.bentobox.bentobox.nms.v1_20_6_R0_1_SNAPSHOT;
+
+import org.bukkit.block.data.BlockData;
+import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
+
+import net.minecraft.core.BlockPosition;
+import net.minecraft.world.level.World;
+import net.minecraft.world.level.chunk.Chunk;
+import world.bentobox.bentobox.nms.CopyWorldRegenerator;
+
+public class WorldRegeneratorImpl extends CopyWorldRegenerator {
+
+ @Override
+ public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData,
+ boolean applyPhysics) {
+ CraftBlockData craft = (CraftBlockData) blockData;
+ World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle();
+ Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ());
+ BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z);
+ // Setting the block to air before setting to another state prevents some console errors
+ nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics);
+ nmsChunk.a(bp, craft.getState(), applyPhysics);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/PasteHandlerImpl.java
new file mode 100644
index 000000000..ece376eb2
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/PasteHandlerImpl.java
@@ -0,0 +1,52 @@
+package world.bentobox.bentobox.nms.v1_21_R0_1_SNAPSHOT;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
+
+import net.minecraft.core.BlockPosition;
+import net.minecraft.world.level.block.state.IBlockData;
+import net.minecraft.world.level.chunk.Chunk;
+import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.nms.PasteHandler;
+import world.bentobox.bentobox.util.DefaultPasteUtil;
+import world.bentobox.bentobox.util.Util;
+
+public class PasteHandlerImpl implements PasteHandler {
+
+ protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState();
+
+ /**
+ * Set the block to the location
+ *
+ * @param island - island
+ * @param location - location
+ * @param bpBlock - blueprint block
+ */
+ @Override
+ public CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) {
+ return Util.getChunkAtAsync(location).thenRun(() -> {
+ Block block = location.getBlock();
+ // Set the block data - default is AIR
+ BlockData bd = DefaultPasteUtil.createBlockData(bpBlock);
+ CraftBlockData craft = (CraftBlockData) bd;
+ net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle();
+ Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4);
+ BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
+ // Setting the block to air before setting to another state prevents some console errors
+ nmsChunk.a(bp, AIR, false);
+ nmsChunk.a(bp, craft.getState(), false);
+ block.setBlockData(bd, false);
+ DefaultPasteUtil.setBlockState(island, block, bpBlock);
+ // Set biome
+ if (bpBlock.getBiome() != null) {
+ block.setBiome(bpBlock.getBiome());
+ }
+ });
+ }
+}
diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_R2/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
similarity index 84%
rename from src/main/java/world/bentobox/bentobox/nms/v1_20_R2/WorldRegeneratorImpl.java
rename to src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
index 2263c01c8..6e1e3855f 100644
--- a/src/main/java/world/bentobox/bentobox/nms/v1_20_R2/WorldRegeneratorImpl.java
+++ b/src/main/java/world/bentobox/bentobox/nms/v1_21_R0_1_SNAPSHOT/WorldRegeneratorImpl.java
@@ -1,8 +1,8 @@
-package world.bentobox.bentobox.nms.v1_20_R2;
+package world.bentobox.bentobox.nms.v1_21_R0_1_SNAPSHOT;
import org.bukkit.block.data.BlockData;
-import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
-import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.World;
diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java
index 681c3cd36..a15e77571 100644
--- a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java
+++ b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java
@@ -7,15 +7,20 @@
package world.bentobox.bentobox.panels.customizable;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import java.io.File;
-import java.util.*;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
@@ -489,7 +494,7 @@ public class IslandCreationPanel
long uses = plugin.getIslands().getIslands(world, user).stream()
.filter(is -> is.getMetaData("bundle")
.map(mdv -> bundle.getDisplayName().equalsIgnoreCase(mdv.asString())
- && !(reset && is.isPrimary())) // If this is a reset, then ignore the use of the island being reset
+ && !(reset && is.isPrimary(user.getUniqueId()))) // If this is a reset, then ignore the use of the island being reset
.orElse(false))
.count();
builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "uses", TextVariables.NUMBER,
diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java
index d9be6a2a9..ba84e4c60 100644
--- a/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java
+++ b/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java
@@ -7,16 +7,21 @@
package world.bentobox.bentobox.panels.customizable;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
import org.apache.commons.lang.WordUtils;
import org.bukkit.Material;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import java.io.File;
-import java.util.*;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
diff --git a/src/main/java/world/bentobox/bentobox/panels/settings/SettingsTab.java b/src/main/java/world/bentobox/bentobox/panels/settings/SettingsTab.java
index 547238e7c..b558cc9d4 100644
--- a/src/main/java/world/bentobox/bentobox/panels/settings/SettingsTab.java
+++ b/src/main/java/world/bentobox/bentobox/panels/settings/SettingsTab.java
@@ -4,6 +4,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
@@ -16,6 +17,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
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.Panel;
@@ -46,16 +48,7 @@ public class SettingsTab implements Tab, ClickHandler {
protected Island island;
protected TabbedPanel parent;
- /**
- * Show a tab of settings
- * @param user - user who is viewing the tab
- * @param type - flag type
- */
- public SettingsTab(User user, Type type) {
- this.user = user;
- this.type = type;
- // Island and world are set when the parent is set.
- }
+ private Map currentMode = new HashMap<>();
/**
* Show a tab of settings
@@ -69,6 +62,21 @@ public class SettingsTab implements Tab, ClickHandler {
this.type = type;
}
+ /**
+ * Show a tab of settings
+ * @param world - world
+ * @param user - user who is viewing the tab
+ * @param type - flag type
+ * @param defaultMode - the default mode to show
+ * @since 2.4.0
+ */
+ public SettingsTab(World world, User user, Type type, Flag.Mode defaultMode) {
+ this.world = world;
+ this.user = user;
+ this.type = type;
+ currentMode.put(user.getUniqueId(), defaultMode);
+ }
+
/**
* @return list of flags that will be shown in this panel
*/
@@ -81,7 +89,7 @@ public class SettingsTab implements Tab, ClickHandler {
// Remove any that are not for this game mode
plugin.getIWM().getAddon(world).ifPresent(gm -> flags.removeIf(f -> !f.getGameModes().isEmpty() && !f.getGameModes().contains(gm)));
// Remove any that are the wrong rank or that will be on the top row
- Flag.Mode mode = plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId());
+ Flag.Mode mode = currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC);
plugin.getIWM().getAddon(world).ifPresent(gm -> flags.removeIf(f -> f.getMode().isGreaterThan(mode) ||
f.getMode().equals(Flag.Mode.TOP_ROW)));
return flags;
@@ -120,13 +128,14 @@ public class SettingsTab implements Tab, ClickHandler {
int i = 0;
// Jump past empty tabs
while (flags.isEmpty() && i++ < Flag.Mode.values().length) {
- plugin.getPlayers().setFlagsDisplayMode(user.getUniqueId(), plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId()).getNext());
+ currentMode.put(user.getUniqueId(), currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC).getNext());
flags = getFlags();
}
- return flags.stream().map(
+ List<@Nullable PanelItem> result = flags.stream().map(
(f -> f.toPanelItem(plugin, user, world, island,
plugin.getIWM().getHiddenFlags(world).contains(f.getID()))))
.toList();
+ return result;
}
@Override
@@ -137,8 +146,9 @@ public class SettingsTab implements Tab, ClickHandler {
icons.put(4, Flags.CHANGE_SETTINGS.toPanelItem(plugin, user, world, island, false));
icons.put(5, Flags.LOCK.toPanelItem(plugin, user, world, island, false));
}
+
// Add the mode icon
- switch (plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId())) {
+ switch (currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC)) {
case ADVANCED -> icons.put(7, new PanelItemBuilder().icon(Material.GOLD_INGOT)
.name(user.getTranslation(PROTECTION_PANEL + "mode.advanced.name"))
.description(user.getTranslation(PROTECTION_PANEL + "mode.advanced.description"), "",
@@ -161,7 +171,8 @@ public class SettingsTab implements Tab, ClickHandler {
.clickHandler(this)
.build());
}
- // Add the reset everything to default - it's only in the player's settings panel
+
+ // Add the reset everything to default - it's only in the player's settings panel
if (island != null && user.getUniqueId().equals(island.getOwner())) {
icons.put(8, new PanelItemBuilder().icon(Material.TNT)
.name(user.getTranslation(PROTECTION_PANEL + "reset-to-default.name"))
@@ -216,7 +227,7 @@ public class SettingsTab implements Tab, ClickHandler {
@Override
public boolean onClick(Panel panel, User user, ClickType clickType, int slot) {
// Cycle the mode
- plugin.getPlayers().setFlagsDisplayMode(user.getUniqueId(), plugin.getPlayers().getFlagsDisplayMode(user.getUniqueId()).getNext());
+ currentMode.put(user.getUniqueId(), currentMode.getOrDefault(user.getUniqueId(), Mode.BASIC).getNext());
if (panel instanceof TabbedPanel tp) {
tp.setActivePage(0);
tp.refreshPanel();
diff --git a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
index cb5e4e4e8..9604aa33f 100644
--- a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
+++ b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java
@@ -113,6 +113,10 @@ public class IslandInfo {
if (island.getPurgeProtected()) {
user.sendMessage("commands.admin.info.purge-protected");
}
+ // Show bundle info if available
+ island.getMetaData("bundle").ifPresent(mdv -> {
+ user.sendMessage("commands.admin.info.bundle", TextVariables.NAME, mdv.asString());
+ });
// Fire info event to allow other addons to add to info
IslandEvent.builder().island(island).location(island.getCenter()).reason(IslandEvent.Reason.INFO)
.involvedPlayer(user.getUniqueId()).addon(addon).admin(true).build();
diff --git a/src/main/java/world/bentobox/bentobox/util/ItemParser.java b/src/main/java/world/bentobox/bentobox/util/ItemParser.java
index c315aa897..f820dca1a 100644
--- a/src/main/java/world/bentobox/bentobox/util/ItemParser.java
+++ b/src/main/java/world/bentobox/bentobox/util/ItemParser.java
@@ -1,14 +1,18 @@
package world.bentobox.bentobox.util;
import java.net.URL;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.MissingFormatArgumentException;
+import java.util.Optional;
+import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
-import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BannerMeta;
import org.bukkit.inventory.meta.Damageable;
@@ -261,7 +265,8 @@ public class ItemParser {
boolean isUpgraded = !part[2].isEmpty() && !part[2].equalsIgnoreCase("1");
boolean isExtended = part[3].equalsIgnoreCase("EXTENDED");
PotionData data = new PotionData(type, isExtended, isUpgraded);
- potionMeta.setBasePotionData(data);
+ // TODO: Set extended and u[graded settings.
+ potionMeta.setBasePotionType(type);
result.setItemMeta(potionMeta);
result.setAmount(Integer.parseInt(part[5]));
return result;
@@ -285,7 +290,7 @@ public class ItemParser {
private static ItemStack parsePotion(String[] part) {
if (part.length == 6) {
BentoBox.getInstance().logWarning("The old potion parsing detected for " + part[0] +
- ". Please update your configs, as SPIGOT changed potion types.");
+ ". Please update your configs, as SPIGOT changed potion types.");
return parsePotionOld(part);
}
@@ -314,7 +319,7 @@ public class ItemParser {
if (result.getItemMeta() instanceof PotionMeta meta) {
PotionType potionType = Enums.getIfPresent(PotionType.class, part[1].toUpperCase(Locale.ENGLISH)).
- or(PotionType.WATER);
+ or(PotionType.WATER);
meta.setBasePotionType(potionType);
result.setItemMeta(meta);
}
@@ -340,7 +345,18 @@ public class ItemParser {
BannerMeta meta = (BannerMeta) result.getItemMeta();
if (meta != null) {
for (int i = 2; i < part.length; i += 2) {
- meta.addPattern(new Pattern(DyeColor.valueOf(part[i + 1]), PatternType.valueOf(part[i])));
+ PatternType pt = Enums.getIfPresent(PatternType.class, part[i]).orNull();
+ if (pt == null) {
+ // Try to convert old to new
+ if (part[i].trim().equals("STRIPE_SMALL")
+ && Enums.getIfPresent(PatternType.class, "SMALL_STRIPES").isPresent()) {
+ pt = PatternType.SMALL_STRIPES;
+ }
+ }
+ DyeColor dc = Enums.getIfPresent(DyeColor.class, part[i + 1]).orNull();
+ if (pt != null && dc != null) {
+ meta.addPattern(new Pattern(dc, pt));
+ }
}
result.setItemMeta(meta);
}
diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java
index 4d833c344..7679b3bcf 100644
--- a/src/main/java/world/bentobox/bentobox/util/Util.java
+++ b/src/main/java/world/bentobox/bentobox/util/Util.java
@@ -1,11 +1,14 @@
package world.bentobox.bentobox.util;
+import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.jar.JarEntry;
@@ -13,6 +16,8 @@ import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Chunk;
@@ -23,25 +28,15 @@ import org.bukkit.World.Environment;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
-import org.bukkit.entity.Allay;
-import org.bukkit.entity.Animals;
-import org.bukkit.entity.Bat;
-import org.bukkit.entity.EnderDragon;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.Flying;
-import org.bukkit.entity.IronGolem;
-import org.bukkit.entity.Monster;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.PufferFish;
-import org.bukkit.entity.Shulker;
-import org.bukkit.entity.Slime;
-import org.bukkit.entity.Snowman;
-import org.bukkit.entity.WaterMob;
+import org.bukkit.entity.*;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import com.google.common.base.Enums;
+import com.google.common.base.Optional;
+
import io.papermc.lib.PaperLib;
import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult;
import world.bentobox.bentobox.BentoBox;
@@ -68,6 +63,11 @@ public class Util {
private static PasteHandler pasteHandler = null;
private static WorldRegenerator regenerator = null;
+ // Bukkit method that was added in 2011
+ // Example value: 1.20.4-R0.1-SNAPSHOT
+ private static final String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_');
+ private static final String pluginPackageName = plugin.getClass().getPackage().getName();
+
private Util() {}
/**
@@ -357,6 +357,10 @@ public class Util {
entity instanceof Allay;
}
+ public static boolean isTamableEntity(Entity entity) {
+ return entity instanceof Tameable && ((Tameable) entity).isTamed();
+ }
+
/*
* PaperLib methods for addons to call
*/
@@ -368,7 +372,7 @@ public class Util {
* @return Future that completes with the result of the teleport
*/
@NonNull
- public static CompletableFuture teleportAsync(@NonNull Entity entity, @NonNull Location location) {
+ public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location) {
return PaperLib.teleportAsync(entity, location);
}
@@ -380,7 +384,8 @@ public class Util {
* @return Future that completes with the result of the teleport
*/
@NonNull
- public static CompletableFuture teleportAsync(@NonNull Entity entity, @NonNull Location location, TeleportCause cause) {
+ public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location,
+ TeleportCause cause) {
return PaperLib.teleportAsync(entity, location, cause);
}
@@ -391,7 +396,8 @@ public class Util {
*/
@NonNull
public static CompletableFuture getChunkAtAsync(@NonNull Location loc) {
- return getChunkAtAsync(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4, true);
+ return getChunkAtAsync(Objects.requireNonNull(loc.getWorld()), loc.getBlockX() >> 4, loc.getBlockZ() >> 4,
+ true);
}
/**
@@ -402,7 +408,7 @@ public class Util {
*/
@NonNull
public static CompletableFuture getChunkAtAsync(@NonNull Location loc, boolean gen) {
- return getChunkAtAsync(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4, gen);
+ return getChunkAtAsync(Objects.requireNonNull(loc.getWorld()), loc.getBlockX() >> 4, loc.getBlockZ() >> 4, gen);
}
/**
@@ -413,7 +419,7 @@ public class Util {
* @return Future that completes with the chunk
*/
@NonNull
- public static CompletableFuture getChunkAtAsync(@NonNull World world, int x, int z) {
+ public static CompletableFuture getChunkAtAsync(@Nonnull World world, int x, int z) {
return getChunkAtAsync(world, x, z, true);
}
@@ -426,7 +432,7 @@ public class Util {
* @return Future that completes with the chunk, or null if the chunk did not exists and generation was not requested.
*/
@NonNull
- public static CompletableFuture getChunkAtAsync(@NonNull World world, int x, int z, boolean gen) {
+ public static CompletableFuture getChunkAtAsync(@Nonnull World world, int x, int z, boolean gen) {
return PaperLib.getChunkAtAsync(world, x, z, gen);
}
@@ -436,7 +442,7 @@ public class Util {
* @return If the chunk is generated or not
*/
public static boolean isChunkGenerated(@NonNull Location loc) {
- return isChunkGenerated(loc.getWorld(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
+ return isChunkGenerated(Objects.requireNonNull(loc.getWorld()), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
}
/**
@@ -446,7 +452,7 @@ public class Util {
* @param z Z coordinate of the chunk to checl
* @return If the chunk is generated or not
*/
- public static boolean isChunkGenerated(@NonNull World world, int x, int z) {
+ public static boolean isChunkGenerated(@Nonnull World world, int x, int z) {
return PaperLib.isChunkGenerated(world, x, z);
}
@@ -457,7 +463,7 @@ public class Util {
* @return The BlockState
*/
@NonNull
- public static BlockStateSnapshotResult getBlockState(@NonNull Block block, boolean useSnapshot) {
+ public static BlockStateSnapshotResult getBlockState(@Nonnull Block block, boolean useSnapshot) {
return PaperLib.getBlockState(block, useSnapshot);
}
@@ -715,19 +721,16 @@ public class Util {
*/
public static WorldRegenerator getRegenerator() {
if (regenerator == null) {
- String serverPackageName = Bukkit.getServer().getClass().getPackage().getName();
- String pluginPackageName = plugin.getClass().getPackage().getName();
- String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1);
WorldRegenerator handler;
try {
- Class> clazz = Class.forName(pluginPackageName + ".nms." + version + ".WorldRegeneratorImpl");
+ Class> clazz = Class.forName(pluginPackageName + ".nms." + bukkitVersion + ".WorldRegeneratorImpl");
if (WorldRegenerator.class.isAssignableFrom(clazz)) {
handler = (WorldRegenerator) clazz.getConstructor().newInstance();
} else {
throw new IllegalStateException("Class " + clazz.getName() + " does not implement WorldRegenerator");
}
} catch (Exception e) {
- plugin.logWarning("No Regenerator found for " + version + ", falling back to Bukkit API.");
+ plugin.logWarning("No Regenerator found for " + bukkitVersion + ", falling back to Bukkit API.");
handler = new world.bentobox.bentobox.nms.fallback.WorldRegeneratorImpl();
}
setRegenerator(handler);
@@ -741,19 +744,22 @@ public class Util {
*/
public static PasteHandler getPasteHandler() {
if (pasteHandler == null) {
- String serverPackageName = Bukkit.getServer().getClass().getPackage().getName();
+
+ // Bukkit method that was added in 2011
+ // Example value: 1.20.4-R0.1-SNAPSHOT
+ String bukkitVersion = "v" + Bukkit.getServer().getBukkitVersion().replace('.', '_').replace('-', '_');
String pluginPackageName = plugin.getClass().getPackage().getName();
- String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1);
+ BentoBox.getInstance().log("Optimizing for " + bukkitVersion);
PasteHandler handler;
try {
- Class> clazz = Class.forName(pluginPackageName + ".nms." + version + ".PasteHandlerImpl");
+ Class> clazz = Class.forName(pluginPackageName + ".nms." + bukkitVersion + ".PasteHandlerImpl");
if (PasteHandler.class.isAssignableFrom(clazz)) {
handler = (PasteHandler) clazz.getConstructor().newInstance();
} else {
throw new IllegalStateException("Class " + clazz.getName() + " does not implement PasteHandler");
}
} catch (Exception e) {
- plugin.logWarning("No PasteHandler found for " + version + ", falling back to Bukkit API.");
+ plugin.logWarning("No PasteHandler found for " + bukkitVersion + ", falling back to Bukkit API.");
handler = new world.bentobox.bentobox.nms.fallback.PasteHandlerImpl();
}
setPasteHandler(handler);
@@ -801,4 +807,38 @@ public class Util {
Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))).
toLowerCase();
}
+
+ /**
+ * Attempts to find the first matching enum constant from an array of possible string representations.
+ * This method sequentially checks each string against the enum constants of the specified enum class
+ * by normalizing the string values to uppercase before comparison, enhancing the likelihood of a match
+ * if the enum constants are defined in uppercase.
+ *
+ * @param enumClass the Class object of the enum type to be checked against
+ * @param values an array of string values which are potential matches for the enum constants
+ * @param the type parameter of the enum
+ * @return the first matching enum constant if a match is found; otherwise, returns null
+ * @throws IOException
+ * @throws NullPointerException if either {@code enumClass} or {@code values} are null
+ */
+ public static > T findFirstMatchingEnum(Class enumClass, String... values) {
+ if (enumClass == null || values == null) {
+ return null;
+ }
+ for (String value : values) {
+ Optional enumConstant = Enums.getIfPresent(enumClass, value.toUpperCase());
+ if (enumConstant.isPresent()) {
+ return enumConstant.get();
+ }
+ }
+ return null; // Return null if no match is found
+ }
+
+ /**
+ * This checks the stack trace for @Test to determine if a test is calling the code and skips.
+ * @return true if it's a test.
+ */
+ public static boolean inTest() {
+ return Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(e -> e.getClassName().endsWith("Test"));
+ }
}
diff --git a/src/main/java/world/bentobox/bentobox/util/heads/HeadCache.java b/src/main/java/world/bentobox/bentobox/util/heads/HeadCache.java
index 63086fddb..bf358f501 100644
--- a/src/main/java/world/bentobox/bentobox/util/heads/HeadCache.java
+++ b/src/main/java/world/bentobox/bentobox/util/heads/HeadCache.java
@@ -4,7 +4,6 @@ import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
-
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java
index db5f8601d..3c46cbaaa 100644
--- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java
+++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java
@@ -9,6 +9,8 @@ import org.bukkit.Bukkit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import world.bentobox.bentobox.util.Util;
+
/**
* Checks and ensures the current server software is compatible with BentoBox.
* @author Poslovitch
@@ -231,7 +233,19 @@ public class ServerCompatibility {
/**
* @since 2.0.0
*/
- V1_20_4(Compatibility.COMPATIBLE);
+ V1_20_4(Compatibility.COMPATIBLE),
+ /**
+ * @since 2.4.0
+ */
+ V1_20_5(Compatibility.COMPATIBLE),
+ /**
+ * @since 2.4.0
+ */
+ V1_20_6(Compatibility.COMPATIBLE),
+ /**
+ * @since 2.4.0
+ */
+ V1_21(Compatibility.COMPATIBLE);
private final Compatibility compatibility;
@@ -273,7 +287,6 @@ public class ServerCompatibility {
if (result == null) {
// Check the server version first
ServerVersion version = getServerVersion();
-
if (version == null || version.getCompatibility().equals(Compatibility.INCOMPATIBLE)) {
// 'Version = null' means that it's not listed. And therefore, it's implicitly incompatible.
result = Compatibility.INCOMPATIBLE;
@@ -315,9 +328,12 @@ public class ServerCompatibility {
*/
@NonNull
public ServerSoftware getServerSoftware() {
- String[] parts = Bukkit.getServer().getVersion().split("-");
+ if (Util.isPaper()) {
+ return ServerSoftware.PAPER;
+ }
+ String[] parts = Bukkit.getServer().getBukkitVersion().split("-");
if (parts.length < 2) {
- return ServerSoftware.UNKNOWN.setName(Bukkit.getServer().getVersion().toUpperCase(Locale.ENGLISH));
+ return ServerSoftware.UNKNOWN.setName(Bukkit.getServer().getBukkitVersion().toUpperCase(Locale.ENGLISH));
}
String serverSoftware = Bukkit.getServer().getVersion().split("-")[1];
try {
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 76f2b8ad4..7989f41ed 100644
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -230,6 +230,7 @@ commands:
banned-players: 'Banned players:'
banned-format: '&c [name]'
unowned: '&c Unowned'
+ bundle: '&a Blueprint Bundle used to create island: &b [name]'
switch:
description: switch on/off protection bypass
op: '&c Ops can always bypass protection. Deop to use command.'
@@ -266,10 +267,13 @@ commands:
reload:
description: reload
tp:
- parameters: [player to teleport]
+ parameters: [player's island]
description: teleport to a player's island
manual: '&c No safe warp found! Manually tp near to &b [location] &c and check
it out'
+ tpuser:
+ parameters: [player's island]
+ description: teleport a player to another player's island
getrank:
parameters: [island owner]
description: get a player's rank on their island or the island of the owner
@@ -510,6 +514,7 @@ commands:
addons: '[prefix_bentobox]&6 Migrating addons'
class: '[prefix_bentobox]&6 Migrating [description]'
migrated: '[prefix_bentobox]&a Migrated'
+ completed: '[prefix_bentobox]&a Completed'
rank:
description: 'list, add, or remove ranks'
parameters: '&a [list | add | remove] [rank reference] [rank value]'
@@ -1226,6 +1231,10 @@ protection:
description: '&a Toggle hive harvesting.'
name: Hive harvesting
hint: Harvesting disabled
+ HURT_TAMED_ANIMALS:
+ description: Toggle hurting. Enabled means that tamed animals can take damage. Disabled means they are invincible.
+ name: Hurt tamed animals
+ hint: Tamed animal hurting disabled
HURT_ANIMALS:
description: Toggle hurting
name: Hurt animals
@@ -1278,6 +1287,7 @@ protection:
LEASH:
description: Toggle use
name: Leash use
+ hint: Leash use disabled
LECTERN:
name: Lecterns
description: |-
@@ -1799,6 +1809,10 @@ enums:
HOT_FLOOR: Hot Floor
CRAMMING: Cramming
DRYOUT: Dryout
+ FREEZE: Freeze
+ KILL: Kill
+ SONIC_BOOM: Sonic Boom
+ WORLD_BORDER: World Border
panel:
credits:
diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml
index c5337504d..38da2cfa4 100644
--- a/src/main/resources/locales/zh-CN.yml
+++ b/src/main/resources/locales/zh-CN.yml
@@ -1,48 +1,53 @@
----
meta:
authors:
+ - GlobalServer
- DuckSoft
- shaokeyibb
+ - Alice-space
+ - CuteLittleSky
+ - dawnTak
banner: RED_BANNER:1:SQUARE_TOP_RIGHT:YELLOW:CROSS:RED:CURLY_BORDER:RED:MOJANG:YELLOW:HALF_HORIZONTAL_MIRROR:RED:HALF_VERTICAL:RED
+
prefixes:
- bentobox: "&6 BentoBox &7 &l > &r "
+ bentobox: '&6BentoBox &7&l> &r '
general:
- success: "&a成功!"
+ success: '&a成功!'
invalid: 无效
errors:
- command-cancelled: "&c命令已取消。"
- no-permission: "&c您无权执行此命令 (&7[permission]&c)。"
- insufficient-rank: "&c您的阶衔没有达到要求 (&7[rank]&c)!"
- use-in-game: "&c这个命令只能在游戏中使用。"
- use-in-console: "&c 该命令仅在控制台中可用。"
- no-team: "&c您目前没有团队!"
- no-island: "&c您现在没有岛屿!"
- player-has-island: "&c该玩家原本已经有岛屿了!"
- player-has-no-island: "&c该玩家没有岛屿!"
- already-have-island: "&c您已经有岛屿了, 不能重复创建!"
- no-safe-location-found: "&c试图将您传送到岛屿上时找不到安全的落脚点。"
- not-owner: "&c您不是该岛屿的主人!"
- player-is-not-owner: "&b[name] &c不是岛屿的主人!"
- not-in-team: "&c该玩家不在您的队伍中!"
- offline-player: "&c该玩家不存在或已离线。"
- unknown-player: "&b[name] &c是未知玩家!"
- general: "&c该命令尚未就绪, 请联系管理员。"
- unknown-command: "&c未知命令。 请使用 &b/[label] help &c查看帮助。"
- wrong-world: "&c在您现在所处的世界里不能这么做!"
- you-must-wait: "&c该命令冷却中, 剩余: &b[number] &c秒。"
- must-be-positive-number: "&c“[number]” 不是有效的正数。"
- not-on-island: "&c 你不在岛上!"
+ command-cancelled: '&c命令已取消.'
+ no-permission: '&c你无权执行此命令. (&7[permission]&c)'
+ insufficient-rank: '&c你的身份等级不够高! (&7[rank]&c)'
+ use-in-game: '&c此命令只能在游戏内使用.'
+ use-in-console: '&c此命令只能在控制台使用.'
+ no-team: '&c你没有加入团队!'
+ no-island: '&c你现在没有岛屿!'
+ player-has-island: '&c该玩家已有岛屿!'
+ player-has-no-island: '&c该玩家没有岛屿!'
+ already-have-island: '&c你已经有岛屿了!'
+ no-safe-location-found: '&c试图将你传送到岛屿上时找不到安全的落脚点.'
+ not-owner: '&c你不是这座岛屿的岛主!'
+ player-is-not-owner: '&b[name]&c不是这座岛屿的岛主!'
+ not-in-team: '&c该玩家在你的团队中!'
+ offline-player: '&c该玩家已离线或不存在.'
+ unknown-player: '&c未知玩家: [name]!'
+ general: '&c该命令尚未就绪 - 请联系管理员'
+ unknown-command: '&c未知命令. 使用&b/[label] help&c查看帮助.'
+ wrong-world: '&c当前世界不能这么做!'
+ you-must-wait: '&c你必须等待[number]秒后才能执行该命令.'
+ must-be-positive-number: '&c[number]不是一个正数.'
+ not-on-island: '&c你不在岛上!'
worlds:
overworld: 主世界
nether: 下界
the-end: 末地
+
commands:
help:
- header: "&7 =========== &c [label] 帮助 &7 ==========="
- syntax: "&b [usage] &a [parameters]&7 : &e [description]"
- syntax-no-parameters: "&b [usage]&7 : &e [description]"
- end: "&7 ================================="
- parameters: "[command]"
+ header: '&7=========== &c[label]帮助 &7==========='
+ syntax: '&b[usage] &a[parameters]&7: &e[description]'
+ syntax-no-parameters: '&b[usage]&7: &e[description]'
+ end: '&7================================='
+ parameters: '[command]'
description: 命令帮助
console: 控制台
admin:
@@ -52,1570 +57,1666 @@ commands:
description: 修改玩家的岛屿已重置次数
set:
description: 设置玩家的岛屿已重置次数
- parameters: " "
- success: "&a已将玩家 &b[name] &a的岛屿已重置次数设置为 &b[number] &a。"
+ parameters:
+ success: '&a已将玩家&b[name]&a的岛屿已重置次数设置为: &b[number]&a.'
reset:
- description: 将玩家的岛屿已重置次数重置为 0
- parameters: ""
- success-everyone: "&a已将&b所有玩家&a的岛屿已重置次数重置为 &b0 &a。"
- success: "&a已将玩家 &b[name] &a的岛屿已重置次数重置为 &b0 &a。"
+ description: '将玩家的岛屿已重置次数重置为: 0'
+ parameters:
+ success-everyone: '&a已将&b所有玩家&a的岛屿已重置次数重置为: &b0&a.'
+ success: '&a已将玩家&b[name]&a的岛屿已重置次数重置为: &b0&a.'
add:
description: 增加玩家的岛屿已重置次数
- parameters: " "
- success: "&a已将玩家 &b[name] &a的岛屿已重置次数增加了 &b[number]&a, 现在为 &b[total] &a。"
+ parameters:
+ success: '&a已将玩家&b[name]&a的岛屿已重置次数增加了&b[number]&a次, 当前已重置次数: &b[total]&a.'
remove:
description: 减少玩家的岛屿已重置次数
- parameters: " "
- success: "&a已将玩家 &b[name] &a的岛屿已重置次数减少了 &b[number]&a, 现在为 &b[total] &a。"
+ parameters:
+ success: '&a已将玩家&b[name]&a的岛屿已重置次数减少了&b[number]&a次, 当前已重置次数: &b[total]&a.'
purge:
- parameters: "[days]"
- description: 清理不活动超过 [days] 天的岛屿
- days-one-or-more: "&c天数必需为至少 1 或 1 以上。"
- purgable-islands: "&a找到 &b[number] &a个不活动可以被清理的岛屿。"
- purge-in-progress: |-
- &c清理正在进行中。
- &c使用 &b/[label] purge stop &c来取消清理。
- number-error: "&c天数必需是整数。"
- confirm: "&d输入 &b/[label] purge confirm &d开始清理。"
- completed: "&a清理工作已完成。"
+ parameters: '[days]'
+ description: 清理超过[days]天不活跃的岛屿
+ days-one-or-more: 天数必需为至少1天或大于1天
+ purgable-islands: '&a发现&b[number]&a个不活跃的岛屿可以被清理.'
+ purge-in-progress: '&c清理正在进行中. 使用&b/[label] purge stop&c取消清理.'
+ number-error: '&c天数必需是整数!'
+ confirm: '&d输入&b/[label] purge confirm&d开始清理'
+ completed: '&a清理工作已完成.'
see-console-for-status: |-
- &a清理已开始。
- &a请参阅控制台或使用 &b/[label] purge status &a来查看清理状态。
- no-purge-in-progress: "&c没有正在进行的清理工作。"
+ &a清理已开始.
+ &a请参阅控制台或使用&b/[label] purge status&a查看清理状态&a.
+ no-purge-in-progress: '&c没有正在进行的清理工作.'
protect:
- description: 开/关 岛屿清理保护, 开启清理保护的岛屿不会被清理。
- move-to-island: "&c您身处的位置并没有岛屿,请到要操作的岛上再试!"
- protecting: "&a已为该岛屿开启清理保护。"
- unprotecting: "&e已关闭该岛屿的清理保护。"
+ description: 开/关 岛屿清理保护, 开启清理保护的岛屿不会被清理.
+ move-to-island: '&c你身处的位置并没有岛屿, 请到要操作的岛上再试!'
+ protecting: '&a已为该岛屿开启清理保护.'
+ unprotecting: '&a已关闭该岛屿的清理保护.'
stop:
description: 停止正在进行的清理工作
- stopping: "&e正在停止清理。"
+ stopping: 正在停止清理
unowned:
- description: 清理被遗弃(无主)的岛屿
- unowned-islands: "&a找到 &b[number] &a个被遗弃(无主)的岛屿。"
+ description: 清理被遗弃(无主)的岛屿
+ unowned-islands: '&a发现&b[number]&a个被遗弃(无主)的岛屿.'
status:
description: 显示清理状态
- status: "&a共有 &b[purgeable] &a个可清理,已清理 &b[purged] &a个 &7(&b[percentage] %&7)&a。"
+ status: '&a共有&b[purgeable]&a个岛屿可清理, 已清理&b[purged]&a个&7(&b[percentage] %&7)&a.'
team:
- description: 管理团队
+ description: 团队管理
add:
- parameters: " "
+ parameters:
description: 将玩家添加到某个岛屿
- name-not-owner: "&c[name] 不是岛屿主人。"
- name-has-island: "&c[name] 已经有岛屿了!"
- success: "&a已将 &b[name] &a加入到 &b[owner] &a的岛屿。"
+ name-not-owner: '&c[name]不是岛主.'
+ name-has-island: '&c[name]已经有岛屿了!'
+ success: '&a已将&b[name]&a加入到&b[owner]&a的岛屿.'
disband:
- parameters: ""
- description: 解散某岛屿所有成员
- use-disband-owner: "&c他不是岛屿主人! 请使用 “disband [owner]” 。"
- disbanded: "&c管理员解散了您的岛屿成员!"
- success: "&b[name] &a的岛屿成员已被解散。"
+ parameters:
+ description: 解散团队
+ use-disband-owner: '&cTA不是岛主! 请使用disband [owner].' # 不知道是什么功能
+ disbanded: '&c管理员解散了你的团队!'
+ success: '&b[name]&a的团队已被解散.'
fix:
- description: 扫描数据库,并修复跨岛屿成员(某些玩家错误地拥有或归属于多个岛屿)。
- scanning: "&e正在扫描数据库..."
- duplicate-owner: "&c玩家 &b[name] &c拥有多个岛屿。"
- player-has: "&c玩家 &b[name] &c拥有 &b[number] 个岛屿。"
- duplicate-member: "&c玩家 &b[name] &c是多个岛屿的成员。"
- rank-on-island: "&c[rank] 在 [xyz]"
- fixed: "&a已修复。"
- done: "&a扫描完毕。"
+ description: 扫描数据库,并修复跨岛屿成员(某些玩家错误地拥有或归属于多个岛屿, 使用该功能会删除玩家主岛以外的岛屿)
+ scanning: 正在扫描数据库...
+ duplicate-owner: '&c数据库中检测到不止1个岛屿的玩家: [name].'
+ player-has: '&c玩家&b[name]&c拥有&b[number]个岛屿.'
+ duplicate-member: '&c玩家 &b[name] &c是多个岛屿的成员.'
+ rank-on-island: '&c在[xyz]的身份等级为: [rank]'
+ fixed: '&a已修复'
+ done: '&a扫描完毕'
kick:
- parameters: ""
- description: 将玩家踢出岛屿
- cannot-kick-owner: "&c您不能踢岛屿主人, 请先踢出岛屿成员。"
- not-in-team: "&c该玩家不是岛屿成员。"
- admin-kicked: "&c管理员将您踢出了岛屿。"
- success: "&a您已将 &b[name] &a从 &b[owner] &a的岛屿里踢出。"
+ parameters:
+ description: 将玩家踢出团队
+ cannot-kick-owner: '&c你不能踢出岛主, 请先踢出团队成员.'
+ not-in-team: '&c该玩家没有团队.'
+ admin-kicked: '&c管理员将你踢出了团队.'
+ success: '&a已将&b[name]&a从&b[owner]&a的岛屿里踢出.'
setowner:
- parameters: ""
- description: 将玩家提升为所在岛屿的岛主
- already-owner: "&c[name] 本身已经是岛主!"
- success: "&a已将 &b[name] &a提升为岛主。"
+ parameters:
+ description: 将当前岛屿的岛主设置为指定玩家
+ already-owner: '&c[name]已经是当前岛屿的岛主了!'
+ must-be-on-island: '&c你必须在岛屿上才能设置所有者'
+ confirmation: '&a你确定要把[name]设置为[xyz]的岛主吗?'
+ success: '&b[name]&a已被设置为当前岛屿的岛主.'
+ extra-islands: '&c警告: 该玩家当前拥有[number]个岛屿, 最多仅可以拥有[max]个岛屿.'
range:
- description: 岛屿范围管理命令
+ description: 岛屿保护范围管理员命令
invalid-value:
- too-low: "&c保护范围必需大于 &b1 &c!"
- too-high: "&c保护范围应等于或小于 &b[number] &c!"
- same-as-before: "&c保护范围已设置为 &b[number] &c!"
+ too-low: '&c保护范围必须大于&b1&c!'
+ too-high: '&c保护范围必须等于或小于&b[number]&c!'
+ same-as-before: '&c保护范围早已被设置为&b[number]&c, 本次操作无改动!'
display:
- already-off: "&c范围指示器已关闭。"
- already-on: "&c范围指示器已打开。"
- description: 开/关 岛屿范围指示器
- hiding: "&2正在隐藏岛屿范围指示器。"
+ already-off: '&c范围指示器已经关闭了'
+ already-on: '&c范围指示器已经开启了'
+ description: 开启/关闭岛屿范围指示器
+ hiding: '&4岛屿范围指示器已禁用'
hint: |-
- &c红色屏障 &f显示所处岛屿的当前保护范围。
- &7灰色粒子 &f显示所处岛屿的最大保护范围。
- &a绿色粒子 &f显示所处岛屿的默认保护范围。
- showing: "&2正在显示岛屿范围指示器。"
+ &c红色屏障 &f显示当前岛屿的当前保护范围.
+ &7灰色粒子 &f显示当前岛屿的最大保护范围.
+ &a绿色粒子 &f显示当前岛屿的默认保护范围(当前岛屿保护范围与默认保护范围不一致时显示).
+ showing: '&2岛屿范围指示器已启用'
set:
- parameters: " "
+ parameters: [island location]
description: 设置岛屿保护范围
- success: "&a给定岛屿保护范围被设置为 &b[number] &a。"
+ success: '&a岛屿保护范围已被设置为: &b[number]&a.'
reset:
- parameters: ""
+ parameters:
description: 将岛屿保护范围重置为默认值
- success: "&a给定岛屿保护范围已被重置为 &b[number] &a。"
+ success: '&a岛屿保护范围已被重置为: &b[number]&a.'
add:
description: 增加岛屿保护范围
- parameters: " "
- success: "&a已将 &b[name]&a 的岛屿保护范围增加到 &b[total] &7(&b+[number]&7)&a。"
+ parameters: [island location]
+ success: '&a玩家&b[name]&a的岛屿保护范围已增加至&b[total]&7(&b+[number]&7)&a.'
remove:
description: 减少岛屿保护范围
- parameters: " "
- success: "&a已将 &b[name]&a 的岛屿保护范围减少到 &b[total] &7(&b-[number]&7)&a。"
+ parameters: [island location]
+ success: '&a玩家&b[name]&a的岛屿保护范围已减少至&b[total]&7(&b-[number]&7)&a.'
register:
- parameters: ""
- description: 将玩家注册到您所处的无人岛
- registered-island: "&a已将 &b[name] &a注册到位于 &b[xyz] &a的无人岛。"
- reserved-island: "&a已为 &b[name] &a预留了位于 &b[xyz] &a的岛屿。"
- already-owned: "&c所处位置为另一位玩家的岛屿范围!"
- no-island-here: "&c这里没有岛屿, 确定要创建一个吗?"
- in-deletion: "&c所处位置正在执行清理工作, 请稍后再试。"
- cannot-make-island: "&c抱歉,不能在这里创建岛屿。 请参阅控制台以获取详细信息。"
+ parameters:
+ description: 将玩家认领到当前的无主岛屿
+ registered-island: '&a已将当前岛屿[xyz]认领给玩家[name].'
+ reserved-island: '&a已为玩家&b[name]&a预留了位于&b[xyz]&a的岛屿.'
+ already-owned: '&c当前岛屿已有所属!'
+ no-island-here: '&c当前位置没有岛屿, 是否要创建一个?'
+ in-deletion: '&c当前位置对应的岛屿正在删除中, 请稍后再试.'
+ cannot-make-island: '&c当前位置无法创建岛屿, 请查看控制台查看可能的错误.'
island-is-spawn: |-
- &c所处位置为出生点岛屿, 您确定要将玩家注册到这个岛屿上?
- &6请再次输入命令以确认。
+ &c当前岛屿是出生点岛屿, 确定要让玩家认领这个岛屿吗?
+ &6请再次输入命令以确认.
unregister:
- parameters: " [x,y,z]"
- description: 注销岛主身份, 但保留岛屿资源。
- unregistered-island: "&a已将岛主 &b[name] &a从位于 &b[xyz] &a的岛屿注销。"
+ parameters: [x,y,z]
+ description: 设置为无主岛屿并保留岛屿建筑
+ unregistered-island: '&a已将[name]位于[xyz]的岛屿设置为无主岛屿.'
+ errors:
+ unknown-island-location: '&c未知岛屿位置'
+ specify-island-location: '&c以x,y,z格式选定岛屿位置'
+ player-has-more-than-one-island: '&c玩家拥有多个岛屿, 请指定一个岛屿.'
info:
- parameters: "[player]"
- description: 获取所在位置或给定玩家的岛屿信息
- no-island: "&c您所处位置没有岛屿。"
- title: "&b&l========== 岛屿信息 ============"
- island-uuid: "&d岛屿编号: &f[uuid]"
- owner: "&d岛屿主人: &f[owner] &7(&f[uuid]&7)"
- last-login: "&d最后登录: &f[date]"
- last-login-date-time-format: EEE MMM dd HH:mm:ss zzz yyyy
- deaths: "&d死亡次数: &f[number] 次"
- resets-left: "&d重置次数: &f[number] 次, 最多: [total] 次"
- team-members-title: "&d岛屿成员:"
- team-owner-format: "&f - [name] [rank]"
- team-member-format: "&7 - [name] [rank]"
- island-protection-center: "&d保护中心: &f[xyz]"
- island-center: "&d岛屿中心: &f[xyz]"
- island-coords: "&d岛屿界线: &f[xz1] 到 [xz2]"
- islands-in-trash: "&d这个岛屿在垃圾桶中。"
- protection-range: "&d保护范围: &f[range]"
- protection-range-bonus-title: "&b 包括以下奖励:"
- protection-range-bonus: 奖金:[number]
- purge-protected: "&d清理保护: &a已开启"
- max-protection-range: "&d历史最大保护范围: &f[range]"
- protection-coords: "&d保护界线: &f[xz1] 到 [xz2]"
- is-spawn: "&d岛屿类别: &a出生点"
- banned-players: "&d封禁玩家:"
- banned-format: "&7 - &c[name]"
- unowned: "&c无人岛"
+ parameters:
+ description: 获取当前岛屿或指定玩家的都信息
+ no-island: '&c你当前没有岛屿...'
+ title: ========== 岛屿信息 ============
+ island-uuid: 'UUID: [uuid]'
+ owner: '岛主: [owner] ([uuid])'
+ last-login: '最后登录: [date]'
+ last-login-date-time-format: yyyy-MM-dd HH:mm:ss zzz
+ deaths: '死亡次数: [number]'
+ resets-left: '重置次数: [number] (至多: [total])'
+ team-members-title: '团队身份:'
+ team-owner-format: '&a[name] [rank]'
+ team-member-format: '&b[name] [rank]'
+ island-protection-center: '岛屿保护中心点: [xyz]'
+ island-center: '岛屿中心: [xyz]'
+ island-coords: '岛屿界线: [xz1] 至 [xz2]'
+ islands-in-trash: '&d岛屿在垃圾桶中.'
+ protection-range: '岛屿保护范围: [range]'
+ protection-range-bonus-title: '&b包含以下奖励:'
+ protection-range-bonus: '奖金: [number]'
+ purge-protected: '&a岛屿处于清理保护状态'
+ max-protection-range: '历史最大保护范围: [range]'
+ protection-coords: '岛屿保护界线: [xz1] 至 [xz2]'
+ is-spawn: '&a为出生点岛屿'
+ banned-players: '封禁玩家:'
+ banned-format: '&c[name]'
+ unowned: '&c无主岛屿'
+ bundle: '&a用于创建岛屿的蓝图方案: &b[name]'
switch:
- description: 开/关 保护规避机制
- op: "&c运维人员始终可以规避岛屿保护。 除非将他们开除。"
- removing: "&c正在删除保护规避..."
- adding: "&a正在添加保护规避..."
+ description: 开启/关闭 保护规避机制
+ op: '&cOP始终可以规避岛屿保护.'
+ removing: '&a正在删除保护规避...'
+ adding: '&a正在添加保护规避...'
switchto:
- parameters: " "
- description: 将玩家的岛屿扔进垃圾桶指定的位置
+ parameters:
+ description: 将玩家岛屿扔进回收站的指定位置
out-of-range: |-
- &c数字必须介于 &b1 &c到 &b[number] &c之间。
- &c使用 &b[label] trash [player] &c来查看岛屿在垃圾桶中的位置。"
- cannot-switch: "&c切换失败。请参阅控制台以获取详细信息。"
- success: "&a成功将玩家的岛屿切换到垃圾桶指定的位置。"
+ &c数字必须在&b1&c和&b[number]&c之间.
+ &c使用&l[label] trash [player]&r&c来查看岛屿在回收站中的位置.
+ cannot-switch: '&c切换失败.请查看控制台以获取详细信息.'
+ success: '&a成功将玩家的岛屿切换到回收站指定的位置.'
trash:
- no-unowned-in-trash: "&c垃圾桶中没有无人岛。"
- no-islands-in-trash: "&c垃圾桶中没有玩家岛屿。"
- parameters: "[player]"
- description: 显示垃圾桶中的无人岛或玩家岛屿
- title: "&d =========== 岛屿垃圾桶 ==========="
- count: "&d&l岛屿: [number]"
- use-switch: "&a使用 &b[label] switchto &a将玩家岛屿扔到垃圾桶中指定的位置。"
- use-emptytrash: "&a使用 &b[label] emptytrash [player] &a来永久删除岛屿。"
+ no-unowned-in-trash: '&c回收站中没有无主岛屿'
+ no-islands-in-trash: '&c该玩家在回收站中没有岛屿'
+ parameters: '[player]'
+ description: 显示回收站中无主岛屿或玩家岛屿
+ title: '&d =========== 岛屿回收站 ==========='
+ count: '&d&l岛屿: [number]'
+ use-switch: '&a使用&l[label] switchto &r&a将玩家岛屿扔到回收站中的指定位置'
+ use-emptytrash: '&a使用&l[label] emptytrash [player]&r&a来永久清除指定玩家的回收站'
emptytrash:
- parameters: "[player]"
- description: 永久删除垃圾桶中的岛屿
- success: "&a垃圾桶已清空。"
+ parameters: '[player]'
+ description: 清理回收站中玩家岛屿或全部的无主岛屿
+ success: '&a回收站已清空.'
version:
- description: 显示 BentoBox 和附加组件的版本
+ description: 显示BentoBox和组件(addon)版本
setrange:
- parameters: " "
- description: 设置玩家岛屿的范围
- range-updated: "&a岛屿范围已更新为 &b[number] &a。"
+ parameters:
+ description: 设置玩家岛屿范围
+ range-updated: '&a岛屿范围已设置为: &b[number]&a.'
reload:
- description: 重载本附加组件
+ description: 重载
tp:
- parameters: " [player to teleport]"
- description: 传送到玩家的岛屿上
- manual: "&c没有找到安全落脚点! 请手动传送到 &b[location] &c附近检查原因。"
+ parameters: [player to teleport]
+ description: 传送到指定玩家的岛屿上
+ manual: '&c没有找到安全的落脚点! 请手动传送至&b[location]&c附近检查原因.'
getrank:
- parameters: " [island owner]"
- description: 获取玩家在岛屿上的阶衔
- rank-is: "&a玩家 &b[name] &a在岛屿上的阶衔是 &b[rank] &a。"
+ parameters: [island owner]
+ description: 获取指定玩家的身份等级
+ rank-is: '&a玩家&b[name]&a在岛屿上的身份等级为: &b[rank]&a.'
setrank:
- parameters: "