Merge remote-tracking branch 'origin/develop' into master

# Conflicts:
#	pom.xml
#	src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
#	src/main/java/world/bentobox/bentobox/database/objects/Island.java
#	src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java
This commit is contained in:
BONNe 2022-10-04 11:56:32 +03:00
commit 1a59ca7785
102 changed files with 5410 additions and 1388 deletions

View File

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

34
pom.xml
View File

@ -63,27 +63,26 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>16</java.version>
<java.version>17</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<mongodb.version>3.12.8</mongodb.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.18-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.19.2-R0.1-SNAPSHOT</spigot.version>
<!-- Might differ from the last Spigot release for short periods
of time -->
<paper.version>1.16.5-R0.1-SNAPSHOT</paper.version>
<paper.version>1.19-R0.1-SNAPSHOT</paper.version>
<bstats.version>2.2.1</bstats.version>
<vault.version>1.7</vault.version>
<placeholderapi.version>2.10.9</placeholderapi.version>
<githubapi.version>d5f5e0bbd8</githubapi.version>
<dynmap.version>3.0-SNAPSHOT</dynmap.version>
<worldedit.version>7.2.5</worldedit.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.20.1</build.version>
<build.version>1.21.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
@ -158,13 +157,9 @@
<id>dynmap-repo</id>
<url>https://repo.mikeprimm.com/</url>
</repository>
<repository>
<id>worldedit-repo</id>
<url>https://maven.sk89q.com/repo/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<!-- This is a temporary reference as the Maven Shade plugin
@ -176,6 +171,11 @@
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
<!-- Spigot NMS required for world regeneration :( -->
<repository>
<id>nms-repo</id>
<url>https://repo.codemc.io/repository/nms/</url>
</repository>
</repositories>
<dependencies>
@ -188,7 +188,7 @@
</dependency>
<!-- Paper API -->
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
@ -230,11 +230,13 @@
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>${mongodb.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
<scope>provided</scope>
</dependency>
<!-- Vault: as their maven repo is down, we need to get it from jitpack -->
<!-- See https://github.com/MilkBowl/VaultAPI/issues/69 -->
@ -258,12 +260,6 @@
<version>${dynmap.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-core</artifactId>
<version>${worldedit.version}</version>
<scope>provided</scope>
</dependency>
<!-- Shaded APIs -->
<dependency>
<groupId>com.github.TheBusyBiscuit</groupId>
@ -346,7 +342,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>16</release>
<release>${java.version}</release>
<!-- <source>${java.version}</source> <target>${java.version}</target> -->
</configuration>
</plugin>
@ -396,7 +392,7 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<source>16</source>
<source>${java.version}</source>
<show>private</show>
<failOnError>false</failOnError>
<additionalJOption>-Xdoclint:none</additionalJOption>

View File

@ -19,7 +19,6 @@ import world.bentobox.bentobox.api.user.Notifier;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.DynmapHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook;
@ -28,7 +27,8 @@ import world.bentobox.bentobox.listeners.BlockEndDragon;
import world.bentobox.bentobox.listeners.DeathListener;
import world.bentobox.bentobox.listeners.JoinLeaveListener;
import world.bentobox.bentobox.listeners.PanelListenerManager;
import world.bentobox.bentobox.listeners.PortalTeleportationListener;
import world.bentobox.bentobox.listeners.teleports.EntityTeleportListener;
import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener;
import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener;
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
@ -227,8 +227,8 @@ public class BentoBox extends JavaPlugin {
hooksManager.registerHook(new MultiverseCoreHook());
islandWorldManager.registerWorldsToMultiverse();
// Register additional hooks
hooksManager.registerHook(new DynmapHook());
// TODO: re-enable after implementation
//hooksManager.registerHook(new DynmapHook());
// TODO: re-enable after rework
//hooksManager.registerHook(new LangUtilsHook());
@ -288,8 +288,10 @@ public class BentoBox extends JavaPlugin {
manager.registerEvents(new PanelListenerManager(), this);
// Standard Nether/End spawns protection
manager.registerEvents(new StandardSpawnProtectionListener(this), this);
// Nether portals
manager.registerEvents(new PortalTeleportationListener(this), this);
// Player portals
manager.registerEvents(new PlayerTeleportListener(this), this);
// Entity portals
manager.registerEvents(new EntityTeleportListener(this), this);
// End dragon blocking
manager.registerEvents(new BlockEndDragon(this), this);
// Banned visitor commands

View File

@ -314,6 +314,12 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.safe-spot-search-vertical-range", since = "1.19.1")
private int safeSpotSearchVerticalRange = 400;
@ConfigComment("By default, if the destination is not safe, the plugin will try to search for a safe spot around the destination.")
@ConfigComment("This allows to change the distance for searching this spot. Larger value will mean longer position search.")
@ConfigComment("This value is also used for valid nether portal linking between dimension.")
@ConfigEntry(path = "island.safe-spot-search-range", since = "1.21.0")
private int safeSpotSearchRange = 16;
/* WEB */
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
@ConfigComment("Disabling this will result in the deactivation of the update checker and of some other")
@ -907,19 +913,65 @@ public class Settings implements ConfigObject {
this.minPortalSearchRadius = minPortalSearchRadius;
}
/**
* Gets safe spot search vertical range.
*
* @return the safe spot search vertical range
*/
public int getSafeSpotSearchVerticalRange() {
return safeSpotSearchVerticalRange;
}
/**
* Sets safe spot search vertical range.
*
* @param safeSpotSearchVerticalRange the safe spot search vertical range
*/
public void setSafeSpotSearchVerticalRange(int safeSpotSearchVerticalRange) {
this.safeSpotSearchVerticalRange = safeSpotSearchVerticalRange;
}
/**
* Is slow deletion boolean.
*
* @return the boolean
*/
public boolean isSlowDeletion() {
return slowDeletion;
}
/**
* Sets slow deletion.
*
* @param slowDeletion the slow deletion
*/
public void setSlowDeletion(boolean slowDeletion) {
this.slowDeletion = slowDeletion;
}
/**
* Gets safe spot search range.
*
* @return the safe spot search range
*/
public int getSafeSpotSearchRange()
{
return safeSpotSearchRange;
}
/**
* Sets safe spot search range.
*
* @param safeSpotSearchRange the safe spot search range
*/
public void setSafeSpotSearchRange(int safeSpotSearchRange)
{
this.safeSpotSearchRange = safeSpotSearchRange;
}
}

View File

@ -262,17 +262,44 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
user.sendMessage("general.errors.use-in-game");
return false;
}
// Check perms, but only if this isn't the console
if (user.isPlayer() && !user.isOp() && getPermission() != null && !getPermission().isEmpty() && !user.hasPermission(getPermission())) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, getPermission());
if (!this.runPermissionCheck(user))
{
// Error message is displayed by permission check.
return false;
}
// Set the user's addon context
user.setAddon(addon);
// Execute and trim args
return canExecute(user, cmdLabel, cmdArgs) && execute(user, cmdLabel, cmdArgs);
}
/**
* This method checks and returns if user has access to the called command.
* It also recursively checks if user has access to the all parent commands.
* @param user User who permission must be checked.
* @return {@code true} is user can execute given command, {@code false} otherwise.
*/
private boolean runPermissionCheck(User user)
{
// Check perms, but only if this isn't the console
if (user.isPlayer() &&
!user.isOp() &&
this.getPermission() != null &&
!this.getPermission().isEmpty() &&
!user.hasPermission(this.getPermission()))
{
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, this.getPermission());
return false;
}
// Recursive permission check to find if user has access to the parent command.
return this.getParent() == null || this.getParent().runPermissionCheck(user);
}
/**
* Get the current composite command based on the arguments
* @param args - arguments
@ -309,6 +336,14 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return plugin.getIslands();
}
/**
* Convenience method to get the island manager
* @return IslandsManager
*/
protected IslandsManager getIslandsManager() {
return plugin.getIslandsManager();
}
/**
* @return this command's sub-level. Top level is 0.
* Every time a command registers with a parent, their level will be set.

View File

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

View File

@ -9,11 +9,7 @@ import world.bentobox.bentobox.api.commands.admin.deaths.AdminDeathsCommand;
import world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand;
import world.bentobox.bentobox.api.commands.admin.range.AdminRangeCommand;
import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand;
import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand;
import world.bentobox.bentobox.api.commands.admin.team.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.commands.admin.team.*;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
@ -42,7 +38,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
*/
@Override
public void setup() {
this.setPermission("admin.*");
this.setPermission("admin");
this.setOnlyPlayer(false);
this.setParametersHelp("commands.admin.help.parameters");
@ -56,6 +52,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminSetrankCommand(this);
new AdminInfoCommand(this);
// Team commands
new AdminTeamCommand(this);
new AdminTeamAddCommand(this);
new AdminTeamKickCommand(this);
new AdminTeamDisbandCommand(this);
@ -94,6 +91,8 @@ public abstract class DefaultAdminCommand extends CompositeCommand {
new AdminSetProtectionCenterCommand(this);
// Delete homes
new AdminDeleteHomesCommand(this);
// Reset name
new AdminResetNameCommand(this);
}
/**

View File

@ -62,20 +62,30 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
return clipboards;
}
protected void showClipboard(User user) {
displayClipboards.putIfAbsent(user, Bukkit.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
if (!user.isPlayer() || !user.getPlayer().isOnline()) {
hideClipboard(user);
}
if (clipboards.containsKey(user.getUniqueId())) {
BlueprintClipboard clipboard = clipboards.get(user.getUniqueId());
paintAxis(user, clipboard);
}
/**
* This method shows clipboard for requested user.
* @param user User who need to see clipboard.
*/
protected void showClipboard(User user)
{
this.displayClipboards.computeIfAbsent(user,
key -> Bukkit.getScheduler().scheduleSyncRepeatingTask(this.getPlugin(), () ->
{
if (!key.isPlayer() || !key.getPlayer().isOnline())
{
this.hideClipboard(key);
}
}, 20, 20));
if (this.clipboards.containsKey(key.getUniqueId()))
{
BlueprintClipboard clipboard = this.clipboards.get(key.getUniqueId());
this.paintAxis(key, clipboard);
}
}, 20, 20));
}
private void paintAxis(User user, BlueprintClipboard clipboard) {
if (clipboard.getPos1() == null || clipboard.getPos2() == null) {
return;

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand {
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboardManager bp = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder());
if (bp.load(user, args.get(0))) {
if (bp.load(user, Util.sanitizeInput(args.get(0)))) {
parent.getClipboards().put(user.getUniqueId(), bp.getClipboard());
return true;
}

View File

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

View File

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

View File

@ -7,10 +7,9 @@ import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import com.google.common.base.Enums;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
@ -24,8 +23,6 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
private static final String DISPLAY = "display";
private static final String SHOW = "show";
private static final String HIDE = "hide";
// Since 1.18, the Particle.BARRIER was replaced by BLOCK_MARKER
private static final Particle BARRIER = Enums.getIfPresent(Particle.class, "BARRIER").or(Enums.getIfPresent(Particle.class, "BLOCK_MARKER").or(Particle.LAVA));
// Map of users to which ranges must be displayed
private final Map<User, Integer> displayRanges = new HashMap<>();
@ -75,7 +72,7 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
getIslands().getIslandAt(user.getLocation()).ifPresent(island -> {
// Draw the island protected area
drawZone(user, BARRIER, null, island, island.getProtectionRange());
drawZone(user, Particle.BLOCK_MARKER, Material.BARRIER.createBlockData(), island, island.getProtectionRange());
// Draw the default protected area if island protected zone is different
if (island.getProtectionRange() != getPlugin().getIWM().getIslandProtectionRange(getWorld())) {
@ -94,7 +91,7 @@ public class AdminRangeDisplayCommand extends CompositeCommand {
displayRanges.remove(user);
}
private void drawZone(User user, Particle particle, Particle.DustOptions dustOptions, Island island, int range) {
private void drawZone(User user, Particle particle, Object dustOptions, Island island, int range) {
Location center = island.getProtectionCenter();
// Get player Y coordinate
int playerY = user.getPlayer().getLocation().getBlockY() + 1;

View File

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

View File

@ -12,6 +12,8 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* /island create - Create an island.
@ -63,13 +65,13 @@ public class IslandCreateCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
// Make island

View File

@ -17,6 +17,8 @@ import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.managers.island.NewIsland.Builder;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -78,13 +80,13 @@ public class IslandResetCommand extends ConfirmableCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null || name.isEmpty()) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
return resetIsland(user, name);

View File

@ -109,6 +109,8 @@ public class IslandTeamInviteCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
// Rare case when invited player is null. Could be a race condition.
if (invitedPlayer == null) return false;
// If that player already has an invite out then retract it.
// Players can only have one invite one at a time - interesting
if (itc.isInvited(invitedPlayer.getUniqueId())) {

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.api.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -31,11 +32,54 @@ public interface WorldSettings extends ConfigObject {
/**
* @return default rank settings for new islands
* @deprecated since 1.21
* Map of Flag, Integer does not allow to load other plugin/addon flags.
* It cannot be replaced with Map of String, Integer due to compatibility issues.
* @see WorldSettings#getDefaultIslandFlagNames()
*/
@Deprecated
Map<Flag, Integer> getDefaultIslandFlags();
/**
* Return map of flags ID's linked to default rank for new island.
* This is necessary so users could specify any flag names in settings file from other plugins and addons.
* Otherwise, Flag reader would mark flag as invalid and remove it.
* Default implementation is compatibility layer so GameModes that are not upgraded still works.
* @since 1.21
* @return default rank settings for new islands.
*/
default Map<String, Integer> getDefaultIslandFlagNames()
{
Map<String, Integer> flags = new HashMap<>();
this.getDefaultIslandFlags().forEach((key, value) -> flags.put(key.getID(), value));
return flags;
}
/**
* @return default settings for new
* @deprecated since 1.21
* Map of Flag, Integer does not allow to load other plugin/addon flags.
* It cannot be replaced with Map of String, Integer due to compatibility issues.
* @see WorldSettings#getDefaultIslandSettingNames()
*/
@Deprecated
Map<Flag, Integer> getDefaultIslandSettings();
/**
* Return map of flags ID's linked to default settings for new island.
* This is necessary so users could specify any flag names in settings file from other plugins and addons.
* Otherwise, Flag reader would mark flag as invalid and remove it.
* Default implementation is compatibility layer so GameModes that are not upgraded still works.
* @since 1.21
* @return default settings for new islands.
*/
default Map<String, Integer> getDefaultIslandSettingNames()
{
Map<String, Integer> flags = new HashMap<>();
this.getDefaultIslandSettings().forEach((key, value) -> flags.put(key.getID(), value));
return flags;
}
/**
* Get the world difficulty
* @return difficulty

View File

@ -256,6 +256,14 @@ public abstract class FlagListener implements Listener {
return plugin.getIslands();
}
/**
* Get the island database manager
* @return the island database manager
*/
protected IslandsManager getIslandsManager() {
return plugin.getIslands();
}
/**
* Get the island world manager
* @return Island World Manager

View File

@ -17,10 +17,13 @@ import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.Particle;
import org.bukkit.Vibration;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
@ -597,22 +600,107 @@ public class User implements MetaDataAble {
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
*/
public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, double x, double y, double z) {
if (particle.equals(Particle.REDSTONE) && dustOptions == null) {
// Security check that will avoid later unexpected exceptions.
throw new IllegalArgumentException("A non-null Particle.DustOptions must be provided when using Particle.REDSTONE as particle.");
public void spawnParticle(Particle particle, Object dustOptions, double x, double y, double z)
{
// Improve particle validation.
switch (particle)
{
case REDSTONE ->
{
if (!(dustOptions instanceof Particle.DustOptions))
{
throw new IllegalArgumentException("A non-null Particle.DustOptions must be provided when using Particle.REDSTONE as particle.");
}
}
case ITEM_CRACK ->
{
if (!(dustOptions instanceof ItemStack))
{
throw new IllegalArgumentException("A non-null ItemStack must be provided when using Particle.ITEM_CRACK as particle.");
}
}
case BLOCK_CRACK, BLOCK_DUST, FALLING_DUST, BLOCK_MARKER ->
{
if (!(dustOptions instanceof BlockData))
{
throw new IllegalArgumentException("A non-null BlockData must be provided when using Particle." + particle + " as particle.");
}
}
case DUST_COLOR_TRANSITION ->
{
if (!(dustOptions instanceof Particle.DustTransition))
{
throw new IllegalArgumentException("A non-null Particle.DustTransition must be provided when using Particle.DUST_COLOR_TRANSITION as particle.");
}
}
case VIBRATION ->
{
if (!(dustOptions instanceof Vibration))
{
throw new IllegalArgumentException("A non-null Vibration must be provided when using Particle.VIBRATION as particle.");
}
}
case SCULK_CHARGE ->
{
if (!(dustOptions instanceof Float))
{
throw new IllegalArgumentException("A non-null Float must be provided when using Particle.SCULK_CHARGE as particle.");
}
}
case SHRIEK ->
{
if (!(dustOptions instanceof Integer))
{
throw new IllegalArgumentException("A non-null Integer must be provided when using Particle.SHRIEK as particle.");
}
}
case LEGACY_BLOCK_CRACK, LEGACY_BLOCK_DUST, LEGACY_FALLING_DUST ->
{
if (!(dustOptions instanceof BlockData))
{
throw new IllegalArgumentException("A non-null MaterialData must be provided when using Particle." + particle + " as particle.");
}
}
}
// Check if this particle is beyond the viewing distance of the server
if (player.getLocation().toVector().distanceSquared(new Vector(x,y,z)) < (Bukkit.getServer().getViewDistance()*256*Bukkit.getServer().getViewDistance())) {
if (particle.equals(Particle.REDSTONE)) {
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))
{
player.spawnParticle(particle, x, y, z, 1, 0, 0, 0, 1, dustOptions);
} else {
}
else if (dustOptions != null)
{
player.spawnParticle(particle, x, y, z, 1, dustOptions);
}
else
{
player.spawnParticle(particle, x, y, z, 1);
}
}
}
/**
* Spawn particles to the player.
* They are only displayed if they are within the 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 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.
*/
public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, double x, double y, double z)
{
this.spawnParticle(particle, (Object) dustOptions, x, y, z);
}
/**
* Spawn particles to the player.
* They are only displayed if they are within the server's view distance.
@ -623,10 +711,12 @@ public class User implements MetaDataAble {
* @param y Y coordinate of the particle to display.
* @param z Z coordinate of the particle to display.
*/
public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, int x, int y, int z) {
spawnParticle(particle, dustOptions, (double) x, (double) y, (double) z);
public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, int x, int y, int z)
{
this.spawnParticle(particle, dustOptions, (double) x, (double) y, (double) z);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/

View File

@ -53,14 +53,14 @@ public class Blueprint {
public String getName() {
if (name == null) name = "unnamed";
// Force lower case
return name.toLowerCase(Locale.ENGLISH);
return name;
}
/**
* @param name the name to set
*/
public Blueprint setName(@NonNull String name) {
// Force lowercase
this.name = name.toLowerCase(Locale.ENGLISH);
this.name = name;
return this;
}
/**

View File

@ -85,7 +85,7 @@ public class BlueprintClipboard {
* @param user - user
* @return true if successful, false if pos1 or pos2 are undefined.
*/
public boolean copy(User user, boolean copyAir) {
public boolean copy(User user, boolean copyAir, boolean copyBiome) {
if (copying) {
user.sendMessage("commands.admin.blueprint.mid-copy");
return false;
@ -120,11 +120,11 @@ public class BlueprintClipboard {
int speed = plugin.getSettings().getPasteSpeed();
List<Vector> vectorsToCopy = getVectors(toCopy);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> copyAsync(world, user, vectorsToCopy, speed, copyAir));
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> copyAsync(world, user, vectorsToCopy, speed, copyAir, copyBiome));
return true;
}
private void copyAsync(World world, User user, List<Vector> vectorsToCopy, int speed, boolean copyAir) {
private void copyAsync(World world, User user, List<Vector> vectorsToCopy, int speed, boolean copyAir, boolean copyBiome) {
copying = false;
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (copying) {
@ -139,7 +139,7 @@ public class BlueprintClipboard {
Math.rint(e.getLocation().getY()),
Math.rint(e.getLocation().getZ())).equals(v))
.collect(Collectors.toList());
if (copyBlock(v.toLocation(world), copyAir, ents)) {
if (copyBlock(v.toLocation(world), copyAir, copyBiome, ents)) {
count++;
}
});
@ -179,7 +179,7 @@ public class BlueprintClipboard {
return r;
}
private boolean copyBlock(Location l, boolean copyAir, Collection<LivingEntity> entities) {
private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collection<LivingEntity> entities) {
Block block = l.getBlock();
if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) {
return false;
@ -203,20 +203,23 @@ public class BlueprintClipboard {
return true;
}
BlueprintBlock b = bluePrintBlock(pos, block);
BlueprintBlock b = bluePrintBlock(pos, block, copyBiome);
if (b != null) {
this.bpBlocks.put(pos, b);
}
return true;
}
private BlueprintBlock bluePrintBlock(Vector pos, Block block) {
private BlueprintBlock bluePrintBlock(Vector pos, Block block, boolean copyBiome) {
// Block state
BlockState blockState = block.getState();
BlueprintBlock b = new BlueprintBlock(block.getBlockData().getAsString());
// Biome
b.setBiome(block.getBiome());
if (copyBiome) {
// Biome
b.setBiome(block.getBiome());
}
// Signs
if (blockState instanceof Sign sign) {
b.setSignLines(Arrays.asList(sign.getLines()));

View File

@ -1,49 +1,27 @@
package world.bentobox.bentobox.blueprints;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Sign;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.nms.PasteHandler;
import world.bentobox.bentobox.util.Util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
/**
* This class pastes the clipboard it is given
* @author tastybento
@ -68,11 +46,9 @@ public class BlueprintPaster {
*/
private static long chunkLoadTime = 0;
private static final String MINECRAFT = "minecraft:";
private static final Map<String, String> BLOCK_CONVERSION = ImmutableMap.of("sign", "oak_sign", "wall_sign", "oak_wall_sign");
private final BentoBox plugin;
private final PasteHandler paster = Util.getPasteHandler();
private final World world;
// The minimum block position (x,y,z)
private Location pos1;
// The maximum block position (x,y,z)
@ -80,6 +56,7 @@ public class BlueprintPaster {
private PasteState pasteState;
private BukkitTask pastingTask;
private BlueprintClipboard clipboard;
private CompletableFuture<Void> currentTask = CompletableFuture.completedFuture(null);
/**
* The Blueprint to paste.
@ -111,6 +88,7 @@ public class BlueprintPaster {
// Calculate location for pasting
this.blueprint = Objects.requireNonNull(clipboard.getBlueprint(), "Clipboard cannot have a null Blueprint");
this.location = location;
this.world = location.getWorld();
this.island = null;
// Paste
@ -128,6 +106,7 @@ public class BlueprintPaster {
this.plugin = plugin;
this.blueprint = bp;
this.island = island;
this.world = world;
// Offset due to bedrock
Vector off = bp.getBedrock() != null ? bp.getBedrock() : new Vector(0,0,0);
// Calculate location for pasting
@ -155,13 +134,12 @@ public class BlueprintPaster {
pasteState = PasteState.CHUNK_LOAD;
// If this is an island OVERWORLD paste, get the island owner.
final Optional<User> owner = Optional.ofNullable(island)
.filter(i -> location.getWorld().getEnvironment().equals(World.Environment.NORMAL))
.map(i -> User.getInstance(i.getOwner()));
// Tell the owner we're pasting blocks and how much time it might take
final Optional<User> owner = Optional.ofNullable(island).map(i -> User.getInstance(i.getOwner()));
// Tell the owner we're pasting blocks and how much time it might take
owner.ifPresent(user -> tellOwner(user, blocks.size(), attached.size(), entities.size(), plugin.getSettings().getPasteSpeed()));
Bits bits = new Bits(blocks, attached, entities,
blocks.entrySet().iterator(), attached.entrySet().iterator(), entities.entrySet().iterator(),
Bits bits = new Bits(blocks, attached, entities,
blocks.entrySet().iterator(), attached.entrySet().iterator(), entities.entrySet().iterator(),
plugin.getSettings().getPasteSpeed());
pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> pasterTask(result, owner, bits), 0L, 1L);
@ -169,6 +147,8 @@ public class BlueprintPaster {
}
private void pasterTask(CompletableFuture<Boolean> result, Optional<User> owner, Bits bits) {
if (!currentTask.isDone()) return;
final int pasteSpeed = plugin.getSettings().getPasteSpeed();
long timer = System.currentTimeMillis();
@ -176,7 +156,7 @@ public class BlueprintPaster {
if (pasteState.equals(PasteState.CHUNK_LOAD)) {
pasteState = PasteState.CHUNK_LOADING;
// Load chunk
Util.getChunkAtAsync(location).thenRun(() -> {
currentTask = Util.getChunkAtAsync(location).thenRun(() -> {
pasteState = PasteState.BLOCKS;
long duration = System.currentTimeMillis() - timer;
if (duration > chunkLoadTime) {
@ -184,34 +164,72 @@ public class BlueprintPaster {
}
});
}
while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && bits.it.hasNext()) {
pasteBlock(location, bits.it.next());
count++;
}
while (pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && bits.it2.hasNext()) {
pasteBlock(location, bits.it2.next());
count++;
}
while (pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && bits.it3.hasNext()) {
pasteEntity(location, bits.it3.next());
count++;
}
// STATE SHIFT
if (pasteState.equals(PasteState.BLOCKS) && !bits.it.hasNext()) {
// Blocks done
// Next paste attachments
pasteState = PasteState.ATTACHMENTS;
}
else if (pasteState.equals(PasteState.ATTACHMENTS) && !bits.it2.hasNext()) {
// Attachments done. Next paste entities
pasteState = PasteState.ENTITIES;
if (bits.entities.size() != 0) {
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(bits.entities.size())));
else if (pasteState.equals(PasteState.BLOCKS) || pasteState.equals(PasteState.ATTACHMENTS)) {
Iterator<Entry<Vector, BlueprintBlock>> it = pasteState.equals(PasteState.BLOCKS) ? bits.it : bits.it2;
if (it.hasNext()) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
// Paste blocks
while (count < pasteSpeed) {
if (!it.hasNext()) {
break;
}
Entry<Vector, BlueprintBlock> entry = it.next();
Location pasteTo = location.clone().add(entry.getKey());
// pos1 and pos2 update
updatePos(pasteTo);
BlueprintBlock block = entry.getValue();
blockMap.put(pasteTo, block);
count++;
}
if (!blockMap.isEmpty()) {
currentTask = paster.pasteBlocks(island, world, blockMap);
}
} else {
if (pasteState.equals(PasteState.BLOCKS)) {
// Blocks done
// Next paste attachments
pasteState = PasteState.ATTACHMENTS;
} else {
// Attachments done. Next paste entities
pasteState = PasteState.ENTITIES;
if (bits.entities.size() != 0) {
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(bits.entities.size())));
}
}
}
}
else if (pasteState.equals(PasteState.ENTITIES) && !bits.it3.hasNext()) {
pasteState = PasteState.DONE;
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done"));
else if (pasteState.equals(PasteState.ENTITIES)) {
if (bits.it3().hasNext()) {
Map<Location, List<BlueprintEntity>> entityMap = new HashMap<>();
// Paste entities
while (count < pasteSpeed) {
if (!bits.it3().hasNext()) {
break;
}
Entry<Vector, List<BlueprintEntity>> entry = bits.it3().next();
int x = location.getBlockX() + entry.getKey().getBlockX();
int y = location.getBlockY() + entry.getKey().getBlockY();
int z = location.getBlockZ() + entry.getKey().getBlockZ();
Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5));
List<BlueprintEntity> entities = entry.getValue();
entityMap.put(center, entities);
count++;
}
if (!entityMap.isEmpty()) {
currentTask = paster.pasteEntities(island, world, entityMap);
}
} else {
pasteState = PasteState.DONE;
String world = switch (location.getWorld().getEnvironment()) {
case NETHER -> owner.map(user -> user.getTranslation("general.worlds.nether")).orElse("");
case THE_END -> owner.map(user -> user.getTranslation("general.worlds.the-end")).orElse("");
default -> owner.map(user -> user.getTranslation("general.worlds.overworld")).orElse("");
};
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.dimension-done", "[world]", world));
}
}
else if (pasteState.equals(PasteState.DONE)) {
// All done. Cancel task
@ -219,7 +237,7 @@ public class BlueprintPaster {
if (island == null && clipboard != null) {
clipboard.setPos1(pos1);
clipboard.setPos2(pos2);
}
}
pasteState = PasteState.CANCEL;
result.complete(true);
} else if (pasteState.equals(PasteState.CANCEL)) {
@ -229,142 +247,13 @@ public class BlueprintPaster {
}
}
private void tellOwner(User user, int blocksSize, int attachedSize, int entitiesSize, int pasteSpeed) {
private void tellOwner(User user, int blocksSize, int attachedSize, int entitiesSize, int pasteSpeed) {
// Estimated time:
double total = (double) blocksSize + attachedSize + entitiesSize;
BigDecimal time = BigDecimal.valueOf(total / (pasteSpeed * 20.0D) + (chunkLoadTime / 1000.0D)).setScale(1, RoundingMode.UP);
user.sendMessage("commands.island.create.pasting.estimated-time", TextVariables.NUMBER, String.valueOf(time.doubleValue()));
// We're pasting blocks!
user.sendMessage("commands.island.create.pasting.blocks", TextVariables.NUMBER, String.valueOf(blocksSize + attachedSize));
}
private void pasteBlock(Location location, Entry<Vector, BlueprintBlock> entry) {
World world = location.getWorld();
Location pasteTo = location.clone().add(entry.getKey());
BlueprintBlock bpBlock = entry.getValue();
Util.getChunkAtAsync(pasteTo).thenRun(() -> {
Block block = pasteTo.getBlock();
// Set the block data - default is AIR
BlockData bd;
try {
bd = Bukkit.createBlockData(bpBlock.getBlockData());
} catch (Exception e) {
bd = convertBlockData(world, bpBlock);
}
block.setBlockData(bd, false);
setBlockState(block, bpBlock);
// Set biome
if (bpBlock.getBiome() != null) {
block.setBiome(bpBlock.getBiome());
}
// pos1 and pos2 update
updatePos(block.getLocation());
});
}
/**
* Tries to convert the BlockData to a newer version, and logs a warning if it fails to do so.
* @return the converted BlockData or a default AIR BlockData.
* @since 1.6.0
*/
private BlockData convertBlockData(World world, BlueprintBlock block) {
BlockData blockData = Bukkit.createBlockData(Material.AIR);
try {
for (Entry<String, String> en : BLOCK_CONVERSION.entrySet()) {
if (block.getBlockData().startsWith(MINECRAFT + en.getKey())) {
blockData = Bukkit.createBlockData(block.getBlockData().replace(MINECRAFT + en.getKey(), MINECRAFT + en.getValue()));
break;
}
}
} catch (IllegalArgumentException e) {
// This may happen if the block type is no longer supported by the server
plugin.logWarning("Blueprint references materials not supported on this server version.");
plugin.logWarning("Load blueprint manually, check and save to fix for this server version.");
plugin.logWarning("World: " + world.getName() + "; Failed block data: " + block.getBlockData());
}
return blockData;
}
private void pasteEntity(Location location, Entry<Vector, List<BlueprintEntity>> entry) {
int x = location.getBlockX() + entry.getKey().getBlockX();
int y = location.getBlockY() + entry.getKey().getBlockY();
int z = location.getBlockZ() + entry.getKey().getBlockZ();
setEntity(new Location(location.getWorld(), x, y, z), entry.getValue());
}
/**
* Handles signs, chests and mob spawner blocks
* @param block - block
* @param bpBlock - config
*/
private void setBlockState(Block block, BlueprintBlock bpBlock) {
// Get the block state
BlockState bs = block.getState();
// Signs
if (bs instanceof org.bukkit.block.Sign sign) {
writeSign(block, bpBlock.getSignLines(), bpBlock.isGlowingText());
}
// Chests, in general
if (bs instanceof InventoryHolder holder) {
Inventory ih = holder.getInventory();
// Double chests are pasted as two blocks so inventory is filled twice.
// This code stops over-filling for the first block.
bpBlock.getInventory().forEach(ih::setItem);
}
// Mob spawners
if (bs instanceof CreatureSpawner spawner) {
setSpawner(spawner, bpBlock.getCreatureSpawner());
}
// Banners
if (bs instanceof Banner banner && bpBlock.getBannerPatterns() != null) {
bpBlock.getBannerPatterns().removeIf(Objects::isNull);
banner.setPatterns(bpBlock.getBannerPatterns());
banner.update(true, false);
}
}
private void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner s) {
spawner.setSpawnedType(s.getSpawnedType());
spawner.setMaxNearbyEntities(s.getMaxNearbyEntities());
spawner.setMaxSpawnDelay(s.getMaxSpawnDelay());
spawner.setMinSpawnDelay(s.getMinSpawnDelay());
spawner.setDelay(s.getDelay());
spawner.setRequiredPlayerRange(s.getRequiredPlayerRange());
spawner.setSpawnRange(s.getSpawnRange());
spawner.update(true, false);
}
/**
* Sets any entity that is in this location
* @param location - location
* @param list - list of entities to paste
*/
private void setEntity(Location location, List<BlueprintEntity> list) {
list.stream().filter(k -> k.getType() != null).forEach(k -> {
// Center, and just a bit high
Location center = location.add(new Vector(0.5, 0.5, 0.5));
Util.getChunkAtAsync(center).thenRun(() -> {
LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, k.getType());
if (k.getCustomName() != null) {
String customName = k.getCustomName();
if (island != null) {
// Parse any placeholders in the entity's name, if the owner's connected (he should)
Player owner = User.getInstance(island.getOwner()).getPlayer();
if (owner != null) {
// Parse for the player's name first (in case placeholders might need it)
customName = customName.replace(TextVariables.NAME, owner.getName());
// Now parse the placeholders
customName = plugin.getPlaceholdersManager().replacePlaceholders(owner, customName);
}
}
// Actually set the custom name
e.setCustomName(customName);
}
k.configureEntity(e);
});
});
user.sendMessage("commands.island.create.pasting.blocks", TextVariables.NUMBER, String.valueOf(blocksSize + attachedSize));
}
/**
@ -397,50 +286,4 @@ public class BlueprintPaster {
pos2.setZ(l.getBlockZ());
}
}
private void writeSign(final Block block, final List<String> lines, boolean glow) {
BlockFace bf;
if (block.getType().name().contains("WALL_SIGN")) {
WallSign wallSign = (WallSign)block.getBlockData();
bf = wallSign.getFacing();
} else {
Sign sign = (Sign)block.getBlockData();
bf = sign.getRotation();
}
// Handle spawn sign
if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.SPAWN_HERE)) {
block.setType(Material.AIR);
// Orient to face same direction as sign
Location spawnPoint = new Location(block.getWorld(), block.getX() + 0.5D, block.getY(),
block.getZ() + 0.5D, Util.blockFaceToFloat(bf.getOppositeFace()), 30F);
island.setSpawnPoint(block.getWorld().getEnvironment(), spawnPoint);
return;
}
// Get the name of the player
String name = "";
if (island != null) {
name = plugin.getPlayers().getName(island.getOwner());
}
// Handle locale text for starting sign
org.bukkit.block.Sign s = (org.bukkit.block.Sign)block.getState();
// Sign text must be stored under the addon's name.sign.line0,1,2,3 in the yaml file
if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.START_TEXT)) {
// Get the addon that is operating in this world
String addonName = plugin.getIWM().getAddon(island.getWorld()).map(addon -> addon.getDescription().getName().toLowerCase(Locale.ENGLISH)).orElse("");
if (island.getOwner() != null) {
for (int i = 0; i < 4; i++) {
s.setLine(i, Util.translateColorCodes(plugin.getLocalesManager().getOrDefault(User.getInstance(island.getOwner()),
addonName + ".sign.line" + i,"").replace(TextVariables.NAME, name)));
}
}
} else {
// Just paste
for (int i = 0; i < 4; i++) {
s.setLine(i, lines.get(i));
}
}
s.setGlowingText(glow);
// Update the sign
s.update();
}
}

View File

@ -1,7 +1,5 @@
package world.bentobox.bentobox.blueprints.conversation;
import java.util.Locale;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
@ -18,60 +16,74 @@ import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
public class NamePrompt extends StringPrompt {
public class NamePrompt extends StringPrompt
{
private final GameModeAddon addon;
@Nullable
private final BlueprintBundle bb;
@Nullable
private Blueprint bp;
public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) {
public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb)
{
this.addon = addon;
this.bb = bb;
}
public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) {
public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb)
{
this.addon = addon;
this.bp = bp;
this.bb = bb;
}
@Override
public @NonNull String getPromptText(ConversationContext context) {
User user = User.getInstance((Player)context.getForWhom());
public @NonNull String getPromptText(ConversationContext context)
{
User user = User.getInstance((Player) context.getForWhom());
return user.getTranslation("commands.admin.blueprint.management.name.prompt");
}
@Override
public Prompt acceptInput(ConversationContext context, String input) {
User user = User.getInstance((Player)context.getForWhom());
public Prompt acceptInput(ConversationContext context, String input)
{
User user = User.getInstance((Player) context.getForWhom());
String uniqueId = Util.sanitizeInput(input);
// Convert color codes
input = Util.translateColorCodes(input);
if (ChatColor.stripColor(input).length() > 32) {
context.getForWhom().sendRawMessage("Too long");
if (ChatColor.stripColor(Util.translateColorCodes(input)).length() > 32)
{
context.getForWhom().sendRawMessage(
user.getTranslation("commands.admin.blueprint.management.name.too-long"));
return this;
}
if (bb == null || !bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) {
// Make a uniqueid
StringBuilder uniqueId = new StringBuilder(ChatColor.stripColor(input).toLowerCase(Locale.ENGLISH).replace(" ", "_"));
if (this.bb == null || !this.bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME))
{
// Check if this name is unique
int max = 0;
while (max++ < 32 && addon.getPlugin().getBlueprintsManager().getBlueprintBundles(addon).containsKey(uniqueId.toString())) {
uniqueId.append("x");
}
if (max == 32) {
context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name"));
if (this.addon.getPlugin().getBlueprintsManager().getBlueprintBundles(this.addon).containsKey(uniqueId))
{
context.getForWhom().sendRawMessage(
user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name"));
return this;
}
context.setSessionData("uniqueId", uniqueId.toString());
} else {
// Default stays as default
context.setSessionData("uniqueId", bb.getUniqueId());
context.setSessionData("uniqueId", uniqueId);
}
else
{
// Default stays as default
context.setSessionData("uniqueId", this.bb.getUniqueId());
}
context.setSessionData("name", input);
return new NameSuccessPrompt(addon, bb, bp);
return new NameSuccessPrompt(this.addon, this.bb, this.bp);
}
}
}

View File

@ -15,58 +15,76 @@ import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
import world.bentobox.bentobox.panels.BlueprintManagementPanel;
public class NameSuccessPrompt extends MessagePrompt {
public class NameSuccessPrompt extends MessagePrompt
{
private final GameModeAddon addon;
private BlueprintBundle bb;
private final Blueprint bp;
/**
* Handles the name processing
*
* @param addon - Game Mode addon
* @param bb - Blueprint Bundle
* @param bp - blueprint
*/
public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) {
public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp)
{
this.addon = addon;
this.bb = bb;
this.bp = bp;
}
@Override
public @NonNull String getPromptText(ConversationContext context) {
public @NonNull String getPromptText(ConversationContext context)
{
String name = (String) context.getSessionData("name");
String uniqueId = (String) context.getSessionData("uniqueId");
User user = User.getInstance((Player)context.getForWhom());
// Rename blueprint
if (bp != null) {
BentoBox.getInstance().getBlueprintsManager().renameBlueprint(addon, bp, name);
new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openBB(bb);
} else {
// Blueprint Bundle
if (bb == null) {
// New Blueprint bundle
bb = new BlueprintBundle();
bb.setIcon(Material.RED_WOOL);
} else {
// Rename - remove old named file
BentoBox.getInstance().getBlueprintsManager().deleteBlueprintBundle(addon, bb);
}
bb.setDisplayName(name);
bb.setUniqueId(uniqueId);
BentoBox.getInstance().getBlueprintsManager().addBlueprintBundle(addon, bb);
BentoBox.getInstance().getBlueprintsManager().saveBlueprintBundle(addon, bb);
new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openPanel();
// Set the name
// if successfully
User user = User.getInstance((Player) context.getForWhom());
// Rename blueprint
if (this.bp != null)
{
this.addon.getPlugin().getBlueprintsManager().renameBlueprint(this.addon, this.bp, uniqueId, name);
new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openBB(this.bb);
}
else
{
// Blueprint Bundle
if (this.bb == null)
{
// New Blueprint bundle
this.bb = new BlueprintBundle();
this.bb.setIcon(Material.RED_WOOL);
}
else
{
// Rename - remove old named file
this.addon.getPlugin().getBlueprintsManager().deleteBlueprintBundle(this.addon, this.bb);
}
this.bb.setDisplayName(name);
this.bb.setUniqueId(uniqueId);
this.addon.getPlugin().getBlueprintsManager().addBlueprintBundle(this.addon, this.bb);
this.addon.getPlugin().getBlueprintsManager().saveBlueprintBundle(this.addon, this.bb);
new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openPanel();
// Set the name
}
return user.getTranslation("commands.admin.blueprint.management.description.success");
}
@Override
protected Prompt getNextPrompt(@NonNull ConversationContext context) {
protected Prompt getNextPrompt(@NonNull ConversationContext context)
{
return Prompt.END_OF_CONVERSATION;
}
}

View File

@ -69,7 +69,7 @@ public class BlueprintBundle implements DataObject {
*/
@Override
public String getUniqueId() {
return uniqueId.toLowerCase(Locale.ENGLISH);
return uniqueId;
}
/**
* @param uniqueId the uniqueId to set

View File

@ -56,7 +56,7 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory {
if (Location.class.isAssignableFrom(rawType)) {
// Use our current location adapter for backward compatibility
return (TypeAdapter<T>) new LocationTypeAdapter();
} else if (Biome.class.isAssignableFrom(rawType) && !ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_17_1)) { // TODO: Any better way ?
} else if (Biome.class.isAssignableFrom(rawType)) {
return (TypeAdapter<T>) new BiomeTypeAdapter();
} else if (Enum.class.isAssignableFrom(rawType)) {
return new EnumTypeAdapter(rawType);

View File

@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.GameMode;
import org.bukkit.World.Environment;
import org.bukkit.entity.Player;
import org.bukkit.util.BoundingBox;
@ -38,8 +39,6 @@ import world.bentobox.bentobox.api.metadata.MetaDataAble;
import world.bentobox.bentobox.api.metadata.MetaDataValue;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer;
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer3;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.RanksManager;
@ -158,9 +157,8 @@ public class Island implements DataObject, MetaDataAble {
private boolean purgeProtected = false;
//// Protection flags ////
@Adapter(FlagSerializer.class)
@Expose
private Map<Flag, Integer> flags = new HashMap<>();
private Map<String, Integer> flags = new HashMap<>();
//// Island History ////
@Adapter(LogEntryListAdapter.class)
@ -179,9 +177,8 @@ public class Island implements DataObject, MetaDataAble {
/**
* Used to store flag cooldowns for this island
*/
@Adapter(FlagSerializer3.class)
@Expose
private Map<Flag, Long> cooldowns = new HashMap<>();
private Map<String, Long> cooldowns = new HashMap<>();
/**
* Commands and the rank required to use them for this island
@ -367,13 +364,13 @@ public class Island implements DataObject, MetaDataAble {
* @return flag value
*/
public int getFlag(@NonNull Flag flag) {
return flags.computeIfAbsent(flag, k -> flag.getDefaultRank());
return flags.computeIfAbsent(flag.getID(), k -> flag.getDefaultRank());
}
/**
* @return the flags
*/
public Map<Flag, Integer> getFlags() {
public Map<String, Integer> getFlags() {
return flags;
}
@ -643,6 +640,55 @@ public class Island implements DataObject, MetaDataAble {
return world;
}
/**
* @return the nether world
*/
@Nullable
public World getNetherWorld()
{
return this.getWorld(Environment.NETHER);
}
/**
* @return the end world
*/
@Nullable
public World getEndWorld()
{
return this.getWorld(Environment.THE_END);
}
/**
* This method returns this island world in given environment. This method can return {@code null} if dimension is
* disabled.
* @param environment The environment of the island world.
* @return the world in given environment.
*/
@Nullable
public World getWorld(Environment environment)
{
if (Environment.NORMAL.equals(environment))
{
return this.world;
}
else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled())
{
return this.getPlugin().getIWM().getEndWorld(this.world);
}
else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled())
{
return this.getPlugin().getIWM().getNetherWorld(this.world);
}
else
{
return null;
}
}
/**
* @return the x coordinate of the island center
*/
@ -679,8 +725,13 @@ public class Island implements DataObject, MetaDataAble {
* @param location - location
* @return true if in island space
*/
@SuppressWarnings("ConstantConditions")
public boolean inIslandSpace(Location location) {
return Util.sameWorld(world, location.getWorld()) && inIslandSpace(location.getBlockX(), location.getBlockZ());
return Util.sameWorld(this.world, location.getWorld()) &&
(location.getWorld().getEnvironment().equals(Environment.NORMAL) ||
this.getPlugin().getIWM().isIslandNether(location.getWorld()) ||
this.getPlugin().getIWM().isIslandEnd(location.getWorld())) &&
this.inIslandSpace(location.getBlockX(), location.getBlockZ());
}
/**
@ -693,12 +744,80 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* Returns a {@link BoundingBox} of the full island space.
* Returns a {@link BoundingBox} of the full island space for overworld.
* @return a {@link BoundingBox} of the full island space.
* @since 1.5.2
*/
@SuppressWarnings("ConstantConditions")
@NotNull
public BoundingBox getBoundingBox() {
return new BoundingBox(getMinX(), world.getMinHeight(), getMinZ(), getMaxX(), world.getMaxHeight(), getMaxZ());
return this.getBoundingBox(Environment.NORMAL);
}
/**
* Returns a {@link BoundingBox} of this island's space area in requested dimension.
* @param environment the requested dimension.
* @return a {@link BoundingBox} of this island's space area or {@code null} if island is not created in requested dimension.
* @since 1.21.0
*/
@Nullable
public BoundingBox getBoundingBox(Environment environment)
{
BoundingBox boundingBox;
if (Environment.NORMAL.equals(environment))
{
// Return normal world bounding box.
boundingBox = new BoundingBox(this.getMinX(),
this.world.getMinHeight(),
this.getMinZ(),
this.getMaxX(),
this.world.getMaxHeight(),
this.getMaxZ());
}
else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled())
{
// If end world is generated, return end island bounding box.
//noinspection ConstantConditions
boundingBox = new BoundingBox(this.getMinX(),
this.getEndWorld().getMinHeight(),
this.getMinZ(),
this.getMaxX(),
this.getEndWorld().getMaxHeight(),
this.getMaxZ());
}
else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled())
{
// If nether world is generated, return nether island bounding box.
//noinspection ConstantConditions
boundingBox = new BoundingBox(this.getMinX(),
this.getNetherWorld().getMinHeight(),
this.getMinZ(),
this.getMaxX(),
this.getNetherWorld().getMaxHeight(),
this.getMaxZ());
}
else
{
boundingBox = null;
}
return boundingBox;
}
/**
* Using this method in the filtering for getVisitors and hasVisitors
* @param player The player that must be checked.
* @return true if player is a visitor
*/
private boolean playerIsVisitor(Player player) {
if (player.getGameMode() == GameMode.SPECTATOR) {
return false;
}
return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK;
}
/**
@ -708,9 +827,7 @@ public class Island implements DataObject, MetaDataAble {
*/
@NonNull
public List<Player> getVisitors() {
return Bukkit.getOnlinePlayers().stream()
.filter(player -> onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK)
.collect(Collectors.toList());
return Bukkit.getOnlinePlayers().stream().filter(this::playerIsVisitor).collect(Collectors.toList());
}
/**
@ -722,7 +839,7 @@ public class Island implements DataObject, MetaDataAble {
* @see #getVisitors()
*/
public boolean hasVisitors() {
return Bukkit.getOnlinePlayers().stream().anyMatch(player -> onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK);
return Bukkit.getOnlinePlayers().stream().anyMatch(this::playerIsVisitor);
}
/**
@ -793,19 +910,83 @@ public class Island implements DataObject, MetaDataAble {
* @param target location to check, not null
* @return {@code true} if this location is within this island's protected area, {@code false} otherwise.
*/
@SuppressWarnings("ConstantConditions")
public boolean onIsland(@NonNull Location target) {
return Util.sameWorld(world, target.getWorld()) && target.getBlockX() >= getMinProtectedX() && target.getBlockX() < (getMinProtectedX() + protectionRange * 2) && target.getBlockZ() >= getMinProtectedZ() && target.getBlockZ() < (getMinProtectedZ() + protectionRange * 2);
return Util.sameWorld(this.world, target.getWorld()) &&
(target.getWorld().getEnvironment().equals(Environment.NORMAL) ||
this.getPlugin().getIWM().isIslandNether(target.getWorld()) ||
this.getPlugin().getIWM().isIslandEnd(target.getWorld())) &&
target.getBlockX() >= this.getMinProtectedX() &&
target.getBlockX() < (this.getMinProtectedX() + this.protectionRange * 2) &&
target.getBlockZ() >= this.getMinProtectedZ() &&
target.getBlockZ() < (this.getMinProtectedZ() + this.protectionRange * 2);
}
/**
* Returns a {@link BoundingBox} of this island's protected area.
* Returns a {@link BoundingBox} of this island's protected area for overworld.
* @return a {@link BoundingBox} of this island's protected area.
* @since 1.5.2
*/
@SuppressWarnings("ConstantConditions")
@NotNull
public BoundingBox getProtectionBoundingBox() {
return new BoundingBox(getMinProtectedX(), 0.0D, getMinProtectedZ(), getMaxProtectedX()-1.0D, world.getMaxHeight(), getMaxProtectedZ()-1.0D);
return this.getProtectionBoundingBox(Environment.NORMAL);
}
/**
* Returns a {@link BoundingBox} of this island's protected area.
* @param environment an environment of bounding box area.
* @return a {@link BoundingBox} of this island's protected area or {@code null} if island is not created in required dimension.
* in required dimension.
* @since 1.21.0
*/
@Nullable
public BoundingBox getProtectionBoundingBox(Environment environment)
{
BoundingBox boundingBox;
if (Environment.NORMAL.equals(environment))
{
// Return normal world bounding box.
boundingBox = new BoundingBox(this.getMinProtectedX(),
this.world.getMinHeight(),
this.getMinProtectedZ(),
this.getMaxProtectedX(),
this.world.getMaxHeight(),
this.getMaxProtectedZ());
}
else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled())
{
// If end world is generated, return end island bounding box.
//noinspection ConstantConditions
boundingBox = new BoundingBox(this.getMinProtectedX(),
this.getEndWorld().getMinHeight(),
this.getMinProtectedZ(),
this.getMaxProtectedX(),
this.getEndWorld().getMaxHeight(),
this.getMaxProtectedZ());
}
else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled())
{
// If nether world is generated, return nether island bounding box.
//noinspection ConstantConditions
boundingBox = new BoundingBox(this.getMinProtectedX(),
this.getNetherWorld().getMinHeight(),
this.getMinProtectedZ(),
this.getMaxProtectedX(),
this.getNetherWorld().getMaxHeight(),
this.getMaxProtectedZ());
}
else
{
boundingBox = null;
}
return boundingBox;
}
/**
* Removes a player from the team member map. Generally, you should
* use {@link world.bentobox.bentobox.managers.IslandsManager#removePlayer(World, UUID)}
@ -851,7 +1032,7 @@ public class Island implements DataObject, MetaDataAble {
* @param doSubflags - whether to set subflags
*/
public void setFlag(Flag flag, int value, boolean doSubflags) {
flags.put(flag, value);
flags.put(flag.getID(), value);
// Subflag support
if (doSubflags && flag.hasSubflags()) {
// Ensure that a subflag isn't a subflag of itself or else we're in trouble!
@ -863,7 +1044,7 @@ public class Island implements DataObject, MetaDataAble {
/**
* @param flags the flags to set
*/
public void setFlags(Map<Flag, Integer> flags) {
public void setFlags(Map<String, Integer> flags) {
this.flags = flags;
setChanged();
}
@ -874,11 +1055,13 @@ public class Island implements DataObject, MetaDataAble {
*/
public void setFlagsDefaults() {
BentoBox plugin = BentoBox.getInstance();
Map<Flag, Integer> result = new HashMap<>();
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION))
.forEach(f -> result.put(f, plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank())));
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING))
.forEach(f -> result.put(f, plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank())));
Map<String, Integer> result = new HashMap<>();
plugin.getFlagsManager().getFlags().stream().
filter(f -> f.getType().equals(Flag.Type.PROTECTION)).
forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank())));
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();
}
@ -1120,7 +1303,7 @@ public class Island implements DataObject, MetaDataAble {
public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) {
int newState = state ? 1 : -1;
if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) {
flags.put(flag, newState);
flags.put(flag.getID(), newState);
if (doSubflags && flag.hasSubflags()) {
// If we have circular subflags or a flag is a subflag of itself we are in trouble!
flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true));
@ -1243,6 +1426,15 @@ public class Island implements DataObject, MetaDataAble {
return nether != null && !getCenter().toVector().toLocation(nether).getBlock().getType().isAir();
}
/**
* Checks whether this island has its nether island mode enabled or not.
* @return {@code true} if this island has its nether island enabled, {@code false} otherwise.
* @since 1.21.0
*/
public boolean isNetherIslandEnabled() {
return this.getPlugin().getIWM().isNetherGenerate(this.world) && this.getPlugin().getIWM().isNetherIslands(this.world);
}
/**
* Checks whether this island has its end island generated or not.
* @return {@code true} if this island has its end island generated, {@code false} otherwise.
@ -1254,6 +1446,16 @@ public class Island implements DataObject, MetaDataAble {
}
/**
* Checks whether this island has its end island mode enabled or not.
* @return {@code true} if this island has its end island enabled, {@code false} otherwise.
* @since 1.21.0
*/
public boolean isEndIslandEnabled() {
return this.getPlugin().getIWM().isEndGenerate(this.world) && this.getPlugin().getIWM().isEndIslands(this.world);
}
/**
* Checks if a flag is on cooldown. Only stored in memory so a server restart will reset the cooldown.
* @param flag - flag
@ -1261,10 +1463,10 @@ public class Island implements DataObject, MetaDataAble {
* @since 1.6.0
*/
public boolean isCooldown(Flag flag) {
if (cooldowns.containsKey(flag) && cooldowns.get(flag) > System.currentTimeMillis()) {
if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) {
return true;
}
cooldowns.remove(flag);
cooldowns.remove(flag.getID());
setChanged();
return false;
}
@ -1274,21 +1476,21 @@ public class Island implements DataObject, MetaDataAble {
* @param flag - Flag to cooldown
*/
public void setCooldown(Flag flag) {
cooldowns.put(flag, flag.getCooldown() * 1000L + System.currentTimeMillis());
cooldowns.put(flag.getID(), flag.getCooldown() * 1000L + System.currentTimeMillis());
setChanged();
}
/**
* @return the cooldowns
*/
public Map<Flag, Long> getCooldowns() {
public Map<String, Long> getCooldowns() {
return cooldowns;
}
/**
* @param cooldowns the cooldowns to set
*/
public void setCooldowns(Map<Flag, Long> cooldowns) {
public void setCooldowns(Map<String, Long> cooldowns) {
this.cooldowns = cooldowns;
setChanged();
}
@ -1661,9 +1863,4 @@ public class Island implements DataObject, MetaDataAble {
+ ", cooldowns=" + cooldowns + ", commandRanks=" + commandRanks + ", reserved=" + reserved
+ ", metaData=" + metaData + ", homes=" + homes + ", maxHomes=" + maxHomes + "]";
}
}

View File

@ -0,0 +1,65 @@
package world.bentobox.bentobox.database.objects.adapters;
import org.bukkit.configuration.MemorySection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* This Serializer migrates Map of String, Boolean to Map of String, Integer in serialization process.
* It is necessary because current implementation requires flags to be mapped to Integer value.
* @author BONNe
*/
public class FlagBooleanSerializer implements AdapterInterface<Map<String, Integer>, Map<String, Boolean>>
{
@SuppressWarnings("unchecked")
@Override
public Map<String, Integer> deserialize(Object object)
{
Map<String, Integer> result = new HashMap<>();
if (object == null)
{
return result;
}
// For YAML
if (object instanceof MemorySection section)
{
for (String key : section.getKeys(false))
{
result.put(key, section.getBoolean(key) ? 0 : -1);
}
}
else
{
for (Entry<String, Boolean> en : ((Map<String, Boolean>) object).entrySet())
{
result.put(en.getKey(), en.getValue() ? 0 : -1);
}
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Boolean> serialize(Object object)
{
Map<String, Boolean> result = new HashMap<>();
if (object == null)
{
return result;
}
Map<String, Integer> flags = (Map<String, Integer>) object;
for (Entry<String, Integer> en : flags.entrySet())
{
result.put(en.getKey(), en.getValue() >= 0);
}
return result;
}
}

View File

@ -626,6 +626,8 @@ public class LangUtilsHook extends Hook {
case MUSIC_DISC_11 -> "C418 - 11";
case MUSIC_DISC_WAIT -> "C418 - wait";
case MUSIC_DISC_PIGSTEP -> "Lena Raine - Pigstep";
case MUSIC_DISC_5 -> "Samuel Åberg - 5";
case MUSIC_DISC_OTHERSIDE -> "Lena Raine - otherside";
default -> null;
};
}

View File

@ -17,7 +17,7 @@ import world.bentobox.bentobox.database.objects.Island;
/**
* Abstracts PlayerPortalEvent and EntityPortalEvent
* @author tastybento
*
* @deprecated replaced not used in new listeners.
*/
public class PlayerEntityPortalEvent {

View File

@ -40,7 +40,11 @@ import world.bentobox.bentobox.util.teleport.SafeSpotTeleport;
* Handles teleportation via the Nether/End portals to the Nether and End dimensions of the worlds added by the GameModeAddons.
*
* @author tastybento
* @deprecated replaced by better listeners.
* @see world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener
* @see world.bentobox.bentobox.listeners.teleports.EntityTeleportListener
*/
@Deprecated
public class PortalTeleportationListener implements Listener {
private final BentoBox plugin;
@ -167,7 +171,7 @@ public class PortalTeleportationListener implements Listener {
return false;
}
if (!Bukkit.getServer().getAllowNether()) {
if (!Bukkit.getAllowNether()) {
e.setCancelled(true);
}
@ -256,19 +260,30 @@ public class PortalTeleportationListener implements Listener {
* @param env - environment
* @param toWorld - to world
*/
Location getTo(PlayerEntityPortalEvent e, Environment env, World toWorld) {
Location getTo(PlayerEntityPortalEvent e, Environment env, World toWorld)
{
// Null check - not that useful
if (e.getFrom().getWorld() == null || toWorld == null) {
if (e.getFrom().getWorld() == null || toWorld == null)
{
return null;
}
if (!e.getCanCreatePortal()) {
Location toLocation = e.getIsland().map(island -> island.getSpawnPoint(env)).
orElse(e.getFrom().toVector().toLocation(toWorld));
// Limit Y to the min/max world height.
toLocation.setY(Math.max(Math.min(toLocation.getY(), toWorld.getMaxHeight()), toWorld.getMinHeight()));
if (!e.getCanCreatePortal())
{
// Legacy portaling
return e.getIsland().map(i -> i.getSpawnPoint(env)).orElse(e.getFrom().toVector().toLocation(toWorld));
return toLocation;
}
// Make portals
// For anywhere other than the end - it is the player's location that is used
if (!env.equals(Environment.THE_END)) {
return e.getFrom().toVector().toLocation(toWorld);
if (!env.equals(Environment.THE_END))
{
return toLocation;
}
// If the-end then we want the platform to always be generated in the same place no matter where
// they enter the portal
@ -279,13 +294,15 @@ public class PortalTeleportationListener implements Listener {
int j = z;
int k = y;
// If the from is not a portal, then we have to find it
if (!e.getFrom().getBlock().getType().equals(Material.END_PORTAL)) {
if (!e.getFrom().getBlock().getType().equals(Material.END_PORTAL))
{
// Find the portal - due to speed, it is possible that the player will be below or above the portal
for (k = 0; (k < e.getWorld().getMaxHeight()) && !e.getWorld().getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++);
for (k = toWorld.getMinHeight(); (k < e.getWorld().getMaxHeight()) &&
!e.getWorld().getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++);
}
// Find the maximum x and z corner
for (; (i < x + 5) && e.getWorld().getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++);
for (; (j < z + 5) && e.getWorld().getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++);
for (; (i < x + 5) && e.getWorld().getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++) ;
for (; (j < z + 5) && e.getWorld().getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++) ;
// Mojang end platform generation is:
// AIR
@ -293,7 +310,7 @@ public class PortalTeleportationListener implements Listener {
// OBSIDIAN
// and player is placed on second air block above obsidian.
// If Y coordinate is below 2, then obsidian platform is not generated and player falls in void.
return new Location(toWorld, i, Math.max(2, k), j);
return new Location(toWorld, i, Math.max(toWorld.getMinHeight() + 2, k), j);
}
@ -304,7 +321,9 @@ public class PortalTeleportationListener implements Listener {
* @return true or false
*/
private boolean isMakePortals(GameModeAddon gm, Environment env) {
return env.equals(Environment.NETHER) ? gm.getWorldSettings().isMakeNetherPortals() : gm.getWorldSettings().isMakeEndPortals();
return env.equals(Environment.NETHER) ?
gm.getWorldSettings().isMakeNetherPortals() && Bukkit.getAllowNether() :
gm.getWorldSettings().isMakeEndPortals() && Bukkit.getAllowEnd();
}
/**

View File

@ -1,12 +1,15 @@
package world.bentobox.bentobox.listeners.flags.protection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
@ -26,235 +29,269 @@ import world.bentobox.bentobox.lists.Flags;
* Handle interaction with blocks
* @author tastybento
*/
public class BlockInteractionListener extends FlagListener {
public class BlockInteractionListener extends FlagListener
{
/**
* These cover materials in another server version.
* This avoids run time errors due to unknown enum values, at the expense of a string comparison
* These cover materials in another server version. This avoids run time errors due to unknown enum values, at the
* expense of a string comparison
*/
private final static Map<String, String> stringFlags;
static {
stringFlags = Map.of("RESPAWN_ANCHOR", "PLACE_BLOCKS");
static
{
stringFlags = Map.of(
"ACACIA_CHEST_BOAT", "CHEST",
"BIRCH_CHEST_BOAT", "CHEST",
"JUNGLE_CHEST_BOAT", "CHEST",
"DARK_OAK_CHEST_BOAT", "CHEST",
"MANGROVE_CHEST_BOAT", "CHEST",
"OAK_CHEST_BOAT", "CHEST",
"SPRUCE_CHEST_BOAT", "CHEST");
}
/**
* Handle interaction with blocks
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerInteract(final PlayerInteractEvent e) {
public void onPlayerInteract(final PlayerInteractEvent e)
{
// We only care about the RIGHT_CLICK_BLOCK action.
if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) {
if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null)
{
return;
}
// Check clicked block
checkClickedBlock(e, e.getPlayer(), e.getClickedBlock().getLocation(), e.getClickedBlock().getType());
this.checkClickedBlock(e, e.getPlayer(), e.getClickedBlock().getLocation(), e.getClickedBlock().getType());
// Now check for in-hand items
if (e.getItem() != null && !e.getItem().getType().equals(Material.AIR)) {
if (e.getItem() != null && !e.getItem().getType().equals(Material.AIR))
{
// Boats
if (e.getItem().getType().name().endsWith("_BOAT")) {
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BOAT);
if (e.getItem().getType().name().endsWith("BOAT"))
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BOAT);
}
// Spawn eggs
else if (e.getItem().getType().name().endsWith("_SPAWN_EGG")) {
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.SPAWN_EGGS);
else if (e.getItem().getType().name().endsWith("_SPAWN_EGG"))
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.SPAWN_EGGS);
}
// Now check for in-hand items
if (e.getItem() != null) {
if (e.getItem().getType().name().contains("BOAT")) {
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS);
return;
}
switch (e.getItem().getType()) {
case ENDER_PEARL:
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.ENDER_PEARL);
break;
case BONE_MEAL:
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS);
break;
default:
break;
else if (e.getItem().getType() == Material.ENDER_PEARL)
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.ENDER_PEARL);
}
else if (e.getItem().getType() == Material.BONE_MEAL)
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS);
}
else if (e.getItem().getType() == Material.GLASS_BOTTLE)
{
Block targetedBlock = e.getPlayer().getTargetBlockExact(5, FluidCollisionMode.ALWAYS);
// Check if player is clicking on water or waterlogged block with a bottle.
if (targetedBlock != null && (Material.WATER.equals(targetedBlock.getType()) ||
targetedBlock.getBlockData() instanceof Waterlogged))
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BREWING);
}
}
}
}
/**
* Check if an action can occur on a clicked block
*
* @param e - event called
* @param player - player
* @param loc - location of clicked block
* @param type - material type of clicked block
*/
private void checkClickedBlock(Event e, Player player, Location loc, Material type) {
private void checkClickedBlock(Event e, Player player, Location loc, Material type)
{
// Handle pots
if (type.name().startsWith("POTTED")) {
checkIsland(e, player, loc, Flags.FLOWER_POT);
return;
}
if (Tag.ANVIL.isTagged(type)) {
checkIsland(e, player, loc, Flags.ANVIL);
return;
}
if (Tag.BUTTONS.isTagged(type)) {
checkIsland(e, player, loc, Flags.BUTTON);
return;
}
if (Tag.BEDS.isTagged(type)) {
checkIsland(e, player, loc, Flags.BED);
return;
}
if (Tag.DOORS.isTagged(type)) {
checkIsland(e, player, loc, Flags.DOOR);
return;
}
if (Tag.SHULKER_BOXES.isTagged(type)) {
checkIsland(e, player, loc, Flags.SHULKER_BOX);
return;
}
if (Tag.TRAPDOORS.isTagged(type)) {
checkIsland(e, player, loc, Flags.TRAPDOOR);
if (type.name().startsWith("POTTED"))
{
this.checkIsland(e, player, loc, Flags.FLOWER_POT);
return;
}
switch (type) {
case BEACON:
checkIsland(e, player, loc, Flags.BEACON);
break;
case BREWING_STAND:
case CAULDRON:
checkIsland(e, player, loc, Flags.BREWING);
break;
case BEEHIVE:
case BEE_NEST:
checkIsland(e, player, loc, Flags.HIVE);
break;
case BARREL:
checkIsland(e, player, loc, Flags.BARREL);
break;
case CHEST:
case CHEST_MINECART:
checkIsland(e, player, loc, Flags.CHEST);
break;
case TRAPPED_CHEST:
checkIsland(e, player, loc, Flags.TRAPPED_CHEST);
break;
case FLOWER_POT:
checkIsland(e, player, loc, Flags.FLOWER_POT);
break;
case COMPOSTER:
checkIsland(e, player, loc, Flags.COMPOSTER);
break;
case DISPENSER:
checkIsland(e, player, loc, Flags.DISPENSER);
break;
case DROPPER:
checkIsland(e, player, loc, Flags.DROPPER);
break;
case HOPPER:
case HOPPER_MINECART:
checkIsland(e, player, loc, Flags.HOPPER);
break;
case BLAST_FURNACE:
case CAMPFIRE:
case FURNACE_MINECART:
case FURNACE:
case SMOKER:
checkIsland(e, player, loc, Flags.FURNACE);
break;
case ENCHANTING_TABLE:
checkIsland(e, player, loc, Flags.ENCHANTING);
break;
case ENDER_CHEST:
checkIsland(e, player, loc, Flags.ENDER_CHEST);
break;
case JUKEBOX:
checkIsland(e, player, loc, Flags.JUKEBOX);
break;
case NOTE_BLOCK:
checkIsland(e, player, loc, Flags.NOTE_BLOCK);
break;
case CRAFTING_TABLE:
case CARTOGRAPHY_TABLE:
case GRINDSTONE:
case STONECUTTER:
case LOOM:
checkIsland(e, player, loc, Flags.CRAFTING);
break;
case LEVER:
checkIsland(e, player, loc, Flags.LEVER);
break;
case REDSTONE_WIRE:
case REPEATER:
case COMPARATOR:
case DAYLIGHT_DETECTOR:
checkIsland(e, player, loc, Flags.REDSTONE);
break;
case DRAGON_EGG:
checkIsland(e, player, loc, Flags.DRAGON_EGG);
break;
case END_PORTAL_FRAME:
checkIsland(e, player, loc, Flags.PLACE_BLOCKS);
break;
case ITEM_FRAME:
checkIsland(e, player, loc, Flags.ITEM_FRAME);
break;
case SWEET_BERRY_BUSH:
checkIsland(e, player, loc, Flags.BREAK_BLOCKS);
break;
case CAKE:
checkIsland(e, player, loc, Flags.CAKE);
break;
case OAK_FENCE_GATE:
case SPRUCE_FENCE_GATE:
case BIRCH_FENCE_GATE:
case JUNGLE_FENCE_GATE:
case DARK_OAK_FENCE_GATE:
case ACACIA_FENCE_GATE:
case CRIMSON_FENCE_GATE:
case WARPED_FENCE_GATE:
checkIsland(e, player, loc, Flags.GATE);
break;
default:
if (stringFlags.containsKey(type.name())) {
Optional<Flag> f = BentoBox.getInstance().getFlagsManager().getFlag(stringFlags.get(type.name()));
f.ifPresent(flag -> checkIsland(e, player, loc, flag));
if (Tag.ANVIL.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.ANVIL);
return;
}
if (Tag.BUTTONS.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.BUTTON);
return;
}
if (Tag.BEDS.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.BED);
return;
}
if (Tag.DOORS.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.DOOR);
return;
}
if (Tag.SHULKER_BOXES.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.SHULKER_BOX);
return;
}
if (Tag.TRAPDOORS.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.TRAPDOOR);
return;
}
if (Tag.FENCE_GATES.isTagged(type))
{
this.checkIsland(e, player, loc, Flags.GATE);
}
// TODO: 1.18 compatibility
// if (Tag.ITEMS_CHEST_BOATS.isTagged(type)) {
// this.checkIsland(e, player, loc, Flags.CHEST);
// }
switch (type)
{
case BEACON -> this.checkIsland(e, player, loc, Flags.BEACON);
case BREWING_STAND -> this.checkIsland(e, player, loc, Flags.BREWING);
case BEEHIVE, BEE_NEST -> this.checkIsland(e, player, loc, Flags.HIVE);
case BARREL -> this.checkIsland(e, player, loc, Flags.BARREL);
case CHEST, CHEST_MINECART -> this.checkIsland(e, player, loc, Flags.CHEST);
case TRAPPED_CHEST -> this.checkIsland(e, player, loc, Flags.TRAPPED_CHEST);
case FLOWER_POT -> this.checkIsland(e, player, loc, Flags.FLOWER_POT);
case COMPOSTER -> this.checkIsland(e, player, loc, Flags.COMPOSTER);
case DISPENSER -> this.checkIsland(e, player, loc, Flags.DISPENSER);
case DROPPER -> this.checkIsland(e, player, loc, Flags.DROPPER);
case HOPPER, HOPPER_MINECART -> this.checkIsland(e, player, loc, Flags.HOPPER);
case BLAST_FURNACE, CAMPFIRE, FURNACE_MINECART, FURNACE, SMOKER ->
this.checkIsland(e, player, loc, Flags.FURNACE);
case ENCHANTING_TABLE -> this.checkIsland(e, player, loc, Flags.ENCHANTING);
case ENDER_CHEST -> this.checkIsland(e, player, loc, Flags.ENDER_CHEST);
case JUKEBOX -> this.checkIsland(e, player, loc, Flags.JUKEBOX);
case NOTE_BLOCK -> this.checkIsland(e, player, loc, Flags.NOTE_BLOCK);
case CRAFTING_TABLE, CARTOGRAPHY_TABLE, GRINDSTONE, STONECUTTER, LOOM ->
this.checkIsland(e, player, loc, Flags.CRAFTING);
case LEVER -> this.checkIsland(e, player, loc, Flags.LEVER);
case REDSTONE_WIRE, REPEATER, COMPARATOR, DAYLIGHT_DETECTOR -> this.checkIsland(e, player, loc, Flags.REDSTONE);
case DRAGON_EGG -> this.checkIsland(e, player, loc, Flags.DRAGON_EGG);
case END_PORTAL_FRAME, RESPAWN_ANCHOR -> this.checkIsland(e, player, loc, Flags.PLACE_BLOCKS);
case GLOW_ITEM_FRAME, ITEM_FRAME -> this.checkIsland(e, player, loc, Flags.ITEM_FRAME);
case SWEET_BERRY_BUSH, CAVE_VINES -> this.checkIsland(e, player, loc, Flags.BREAK_BLOCKS);
case CAKE -> this.checkIsland(e, player, loc, Flags.CAKE);
case LAVA_CAULDRON ->
{
if (BlockInteractionListener.holds(player, Material.BUCKET))
{
this.checkIsland(e, player, loc, Flags.COLLECT_LAVA);
}
}
case WATER_CAULDRON ->
{
if (BlockInteractionListener.holds(player, Material.BUCKET))
{
this.checkIsland(e, player, loc, Flags.COLLECT_WATER);
}
else if (BlockInteractionListener.holds(player, Material.GLASS_BOTTLE) ||
BlockInteractionListener.holds(player, Material.POTION))
{
this.checkIsland(e, player, loc, Flags.BREWING);
}
}
case POWDER_SNOW_CAULDRON ->
{
if (BlockInteractionListener.holds(player, Material.BUCKET))
{
this.checkIsland(e, player, loc, Flags.COLLECT_POWDERED_SNOW);
}
}
case CAULDRON ->
{
if (BlockInteractionListener.holds(player, Material.WATER_BUCKET) ||
BlockInteractionListener.holds(player, Material.LAVA_BUCKET) ||
BlockInteractionListener.holds(player, Material.POWDER_SNOW_BUCKET))
{
this.checkIsland(e, player, loc, Flags.BUCKET);
}
else if (BlockInteractionListener.holds(player, Material.POTION))
{
this.checkIsland(e, player, loc, Flags.BREWING);
}
}
default ->
{
if (stringFlags.containsKey(type.name()))
{
Optional<Flag> f = BentoBox.getInstance().getFlagsManager().getFlag(stringFlags.get(type.name()));
f.ifPresent(flag -> this.checkIsland(e, player, loc, flag));
}
}
}
}
/**
* When breaking blocks is allowed, this protects
* specific blocks from being broken, which would bypass the protection.
* For example, player enables break blocks, but chests are still protected
* Fires after the BreakBlocks check.
* When breaking blocks is allowed, this protects specific blocks from being broken, which would bypass the
* protection. For example, player enables break blocks, but chests are still protected Fires after the BreakBlocks
* check.
*
* @param e - event
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlockBreak(final BlockBreakEvent e) {
checkClickedBlock(e, e.getPlayer(), e.getBlock().getLocation(), e.getBlock().getType());
public void onBlockBreak(final BlockBreakEvent e)
{
this.checkClickedBlock(e, e.getPlayer(), e.getBlock().getLocation(), e.getBlock().getType());
}
/**
* Prevents dragon eggs from flying out of an island's protected space
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onDragonEggTeleport(BlockFromToEvent e) {
public void onDragonEggTeleport(BlockFromToEvent e)
{
Block block = e.getBlock();
if (!block.getType().equals(Material.DRAGON_EGG) || !getIWM().inWorld(block.getLocation())) {
if (!block.getType().equals(Material.DRAGON_EGG) || !this.getIWM().inWorld(block.getLocation()))
{
return;
}
// If egg starts in a protected island...
// Cancel if toIsland is not fromIsland or if there is no protected island there
// This protects against eggs dropping into adjacent islands, e.g. island distance and protection range are equal
Optional<Island> fromIsland = getIslands().getProtectedIslandAt(block.getLocation());
Optional<Island> toIsland = getIslands().getProtectedIslandAt(e.getToBlock().getLocation());
Optional<Island> fromIsland = this.getIslands().getProtectedIslandAt(block.getLocation());
Optional<Island> toIsland = this.getIslands().getProtectedIslandAt(e.getToBlock().getLocation());
fromIsland.ifPresent(from -> e.setCancelled(toIsland.map(to -> to != from).orElse(true)));
}
}
/**
* This method returns if player is holding given material in main or offhand.
* @param player Player that must be checked.
* @param material item that mus t be checjed.
* @return {@code true} if player is holding item in main hand or offhand.
*/
private static boolean holds(Player player, Material material)
{
return player.getInventory().getItemInMainHand().getType().equals(material) ||
player.getInventory().getItemInOffHand().getType().equals(material);
}
}

View File

@ -58,19 +58,23 @@ public class BreakBlocksListener extends FlagListener {
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerInteract(final PlayerInteractEvent e) {
public void onPlayerInteract(final PlayerInteractEvent e)
{
// Only handle hitting things
if (!e.getAction().equals(Action.LEFT_CLICK_BLOCK)) {
if (!e.getAction().equals(Action.LEFT_CLICK_BLOCK) || e.getClickedBlock() == null)
{
return;
}
Player p = e.getPlayer();
Location l = e.getClickedBlock().getLocation();
switch (e.getClickedBlock().getType()) {
case CAKE -> checkIsland(e, p, l, Flags.BREAK_BLOCKS);
case SPAWNER -> checkIsland(e, p, l, Flags.BREAK_SPAWNERS);
case DRAGON_EGG -> checkIsland(e, p, l, Flags.DRAGON_EGG);
case HOPPER -> checkIsland(e, p, l, Flags.BREAK_HOPPERS);
default -> {}
switch (e.getClickedBlock().getType())
{
case CAKE -> this.checkIsland(e, p, l, Flags.BREAK_BLOCKS);
case SPAWNER -> this.checkIsland(e, p, l, Flags.BREAK_SPAWNERS);
case DRAGON_EGG -> this.checkIsland(e, p, l, Flags.DRAGON_EGG);
case HOPPER -> this.checkIsland(e, p, l, Flags.BREAK_HOPPERS);
}
}
@ -79,16 +83,26 @@ public class BreakBlocksListener extends FlagListener {
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void onVehicleDamageEvent(VehicleDamageEvent e) {
public void onVehicleDamageEvent(VehicleDamageEvent e)
{
Location l = e.getVehicle().getLocation();
if (getIWM().inWorld(l) && e.getAttacker() instanceof Player p) {
String vehicleType = e.getVehicle().getType().toString();
if (e.getVehicle().getType().equals(EntityType.BOAT)) {
checkIsland(e, p, l, Flags.BOAT);
} else if (vehicleType.contains("MINECART")) {
checkIsland(e, p, l, Flags.MINECART);
} else {
checkIsland(e, p, l, Flags.BREAK_BLOCKS);
if (getIWM().inWorld(l) && e.getAttacker() instanceof Player p)
{
String vehicleType = e.getVehicle().getType().name();
// 1.19 introduced Chest Boat.
if (vehicleType.contains("BOAT"))
{
this.checkIsland(e, p, l, Flags.BOAT);
}
else if (vehicleType.contains("MINECART"))
{
this.checkIsland(e, p, l, Flags.MINECART);
}
else
{
this.checkIsland(e, p, l, Flags.BREAK_BLOCKS);
}
}
}

View File

@ -56,16 +56,27 @@ public class BreedingListener extends FlagListener {
bi.put(EntityType.TURTLE, Collections.singletonList(Material.SEAGRASS));
bi.put(EntityType.PANDA, Collections.singletonList(Material.BAMBOO));
bi.put(EntityType.FOX, Collections.singletonList(Material.SWEET_BERRIES));
if (Enums.getIfPresent(EntityType.class, "BEES").isPresent()) { // 1.15.2
bi.put(EntityType.BEE, Arrays.asList(Material.SUNFLOWER, Material.ORANGE_TULIP, Material.PINK_TULIP,
Material.RED_TULIP, Material.WHITE_TULIP, Material.ALLIUM,
Material.AZURE_BLUET, Material.BLUE_ORCHID, Material.CORNFLOWER,
Material.DANDELION, Material.OXEYE_DAISY, Material.PEONY, Material.POPPY));
}
if (Enums.getIfPresent(EntityType.class, "HOGLIN").isPresent()) {
bi.put(EntityType.HOGLIN, Collections.singletonList(Material.CRIMSON_FUNGUS)); // 1.16.1
bi.put(EntityType.STRIDER, Collections.singletonList(Material.WARPED_FUNGUS)); // 1.16.1
// 1.15+
bi.put(EntityType.BEE, Arrays.asList(Material.SUNFLOWER, Material.ORANGE_TULIP, Material.PINK_TULIP,
Material.RED_TULIP, Material.WHITE_TULIP, Material.ALLIUM,
Material.AZURE_BLUET, Material.BLUE_ORCHID, Material.CORNFLOWER,
Material.DANDELION, Material.OXEYE_DAISY, Material.PEONY, Material.POPPY));
// 1.16+
bi.put(EntityType.HOGLIN, Collections.singletonList(Material.CRIMSON_FUNGUS));
bi.put(EntityType.STRIDER, Collections.singletonList(Material.WARPED_FUNGUS));
// 1.18+
bi.put(EntityType.AXOLOTL, Collections.singletonList(Material.TROPICAL_FISH_BUCKET));
bi.put(EntityType.GOAT, Collections.singletonList(Material.WHEAT));
// 1.19+
// TODO: remove one 1.18 is dropped.
if (Enums.getIfPresent(EntityType.class, "FROG").isPresent()) {
bi.put(EntityType.FROG, Collections.singletonList(Material.SLIME_BALL));
bi.put(EntityType.ALLAY, Collections.singletonList(Material.AMETHYST_SHARD));
}
// Helper
// if (Enums.getIfPresent(EntityType.class, "<name>").isPresent()) {
// bi.put(EntityType.<type>, Collections.singletonList(Material.<material>));
// }
BREEDING_ITEMS = Collections.unmodifiableMap(bi);
}

View File

@ -3,9 +3,10 @@ package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Axolotl;
import org.bukkit.entity.Fish;
import org.bukkit.entity.MushroomCow;
import org.bukkit.entity.Player;
import org.bukkit.entity.TropicalFish;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
@ -31,7 +32,7 @@ public class BucketListener extends FlagListener {
public void onBucketEmpty(final PlayerBucketEmptyEvent e) {
// This is where the water or lava actually will be dumped
Block dumpBlock = e.getBlockClicked().getRelative(e.getBlockFace());
checkIsland(e, e.getPlayer(), dumpBlock.getLocation(), Flags.BUCKET);
this.checkIsland(e, e.getPlayer(), dumpBlock.getLocation(), Flags.BUCKET);
}
/**
@ -42,19 +43,35 @@ public class BucketListener extends FlagListener {
public void onBucketFill(final PlayerBucketFillEvent e) {
Player p = e.getPlayer();
Location l = e.getBlockClicked().getLocation();
if (e.getItemStack() == null)
{
// Null-pointer check.
return;
}
// Check filling of various liquids
switch (e.getItemStack().getType()) {
case LAVA_BUCKET -> checkIsland(e, p, l, Flags.COLLECT_LAVA);
case WATER_BUCKET -> checkIsland(e, p, l, Flags.COLLECT_WATER);
case MILK_BUCKET -> checkIsland(e, p, l, Flags.MILKING);
default -> checkIsland(e, p, l, Flags.BUCKET);
switch (e.getItemStack().getType())
{
case LAVA_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_LAVA);
case WATER_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_WATER);
case POWDER_SNOW_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_POWDERED_SNOW);
case MILK_BUCKET -> this.checkIsland(e, p, l, Flags.MILKING);
default -> this.checkIsland(e, p, l, Flags.BUCKET);
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onTropicalFishScooping(final PlayerInteractEntityEvent e) {
if (e.getRightClicked() instanceof TropicalFish && e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET)) {
checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.FISH_SCOOPING);
if (e.getRightClicked() instanceof Fish &&
e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET))
{
this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.FISH_SCOOPING);
}
else if (e.getRightClicked() instanceof Axolotl &&
e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET))
{
this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.AXOLOTL_SCOOPING);
}
}

View File

@ -1,10 +1,10 @@
package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.entity.EntityType;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.entity.SheepDyeWoolEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
@ -22,24 +22,35 @@ public class DyeListener extends FlagListener {
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerInteract(final PlayerInteractEvent e) {
if (e.getClickedBlock() == null || e.getItem() == null) {
public void onPlayerInteract(final PlayerInteractEvent e)
{
if (e.getClickedBlock() == null || e.getItem() == null)
{
return;
}
if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && e.getClickedBlock().getType().name().contains("SIGN")
&& e.getItem().getType().name().contains("DYE")) {
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.DYE);
if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) &&
e.getClickedBlock().getType().name().contains("SIGN") &&
(e.getItem().getType().name().contains("DYE") || e.getItem().getType().equals(Material.GLOW_INK_SAC)))
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.DYE);
}
}
/**
* Prevents from interacting with sheep.
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerInteract(final PlayerInteractEntityEvent e) {
// We cannot use SheepDyeWoolEvent since it doesn't provide who dyed the sheep
if (e.getRightClicked().getType().equals(EntityType.SHEEP)
&& (e.getPlayer().getInventory().getItemInMainHand().getType().name().contains("DYE")
|| e.getPlayer().getInventory().getItemInOffHand().getType().name().contains("DYE"))) {
checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.DYE);
public void onPlayerInteract(final SheepDyeWoolEvent e)
{
if (e.getPlayer() == null)
{
// Sheep is not dyed by the player.
return;
}
this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.DYE);
}
}

View File

@ -2,13 +2,7 @@ package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Animals;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.entity.Villager;
import org.bukkit.entity.WanderingTrader;
import org.bukkit.entity.*;
import org.bukkit.entity.minecart.RideableMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@ -17,6 +11,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
* Handles interaction with entities like armor stands
@ -34,34 +30,68 @@ public class EntityInteractListener extends FlagListener {
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent e) {
public void onPlayerInteractEntity(PlayerInteractEntityEvent e)
{
Player p = e.getPlayer();
Location l = e.getRightClicked().getLocation();
if (e.getRightClicked() instanceof Vehicle) {
// Animal riding
if (e.getRightClicked() instanceof Animals) {
checkIsland(e, p, l, Flags.RIDING);
if (e.getRightClicked() instanceof Vehicle)
{
if (e.getRightClicked() instanceof Animals)
{
// Animal riding
this.checkIsland(e, p, l, Flags.RIDING);
}
// Minecart riding
else if (e.getRightClicked() instanceof RideableMinecart) {
checkIsland(e, p, l, Flags.MINECART);
else if (e.getRightClicked() instanceof RideableMinecart)
{
// Minecart riding
this.checkIsland(e, p, l, Flags.MINECART);
}
// Boat riding
else if (e.getRightClicked() instanceof Boat) {
checkIsland(e, p, l, Flags.BOAT);
else if (!ServerCompatibility.getInstance().isVersion(
ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
e.getPlayer().isSneaking() && e.getRightClicked() instanceof ChestBoat)
{
// Access to chest boat since 1.19
this.checkIsland(e, p, l, Flags.CHEST);
}
else if (e.getRightClicked() instanceof Boat)
{
// Boat riding
this.checkIsland(e, p, l, Flags.BOAT);
}
}
// Villager trading
else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader) {
else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader)
{
// Villager trading
// Check naming and check trading
checkIsland(e, p, l, Flags.TRADING);
if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) {
checkIsland(e, p, l, Flags.NAME_TAG);
this.checkIsland(e, p, l, Flags.TRADING);
if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG))
{
this.checkIsland(e, p, l, Flags.NAME_TAG);
}
}
// Name tags
else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) {
checkIsland(e, p, l, Flags.NAME_TAG);
else if (!ServerCompatibility.getInstance().isVersion(
ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
e.getRightClicked() instanceof Allay)
{
// Allay item giving/taking
this.checkIsland(e, p, l, Flags.ALLAY);
// Check naming
if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG))
{
this.checkIsland(e, p, l, Flags.NAME_TAG);
}
}
else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG))
{
// Name tags
this.checkIsland(e, p, l, Flags.NAME_TAG);
}
}
}

View File

@ -14,7 +14,6 @@ import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Parrot;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
@ -31,7 +30,6 @@ import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
@ -50,17 +48,24 @@ public class HurtingListener extends FlagListener {
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityDamage(final EntityDamageByEntityEvent e) {
public void onEntityDamage(final EntityDamageByEntityEvent e)
{
// Mobs being hurt
if (Util.isPassiveEntity(e.getEntity())) {
respond(e, e.getDamager(), Flags.HURT_ANIMALS);
} else if (e.getEntity() instanceof Villager || e.getEntityType().name().equals("WANDERING_TRADER")) { // TODO: Simplify when 1.13.2 support is dropped
respond(e, e.getDamager(), Flags.HURT_VILLAGERS);
} else if (Util.isHostileEntity(e.getEntity())) {
respond(e, e.getDamager(), Flags.HURT_MONSTERS);
if (Util.isPassiveEntity(e.getEntity()))
{
this.respond(e, e.getDamager(), Flags.HURT_ANIMALS);
}
else if (e.getEntity() instanceof AbstractVillager)
{
this.respond(e, e.getDamager(), Flags.HURT_VILLAGERS);
}
else if (Util.isHostileEntity(e.getEntity()))
{
this.respond(e, e.getDamager(), Flags.HURT_MONSTERS);
}
}
/**
* Finds the true attacker, even if the attack was via a projectile
* @param e - event
@ -159,12 +164,6 @@ public class HurtingListener extends FlagListener {
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void onLingeringPotionSplash(final LingeringPotionSplashEvent e) {
// TODO Switch this to 1.13 when we move to 1.14 officially
if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_14, ServerCompatibility.ServerVersion.V1_14_1)) {
// We're disabling this check for non-1.14 servers.
return;
}
// Try to get the shooter
Projectile projectile = e.getEntity();
if (projectile.getShooter() instanceof Player) {

View File

@ -13,6 +13,7 @@ import org.bukkit.block.Furnace;
import org.bukkit.block.Hopper;
import org.bukkit.block.ShulkerBox;
import org.bukkit.entity.Animals;
import org.bukkit.entity.ChestBoat;
import org.bukkit.entity.NPC;
import org.bukkit.entity.Player;
import org.bukkit.entity.minecart.HopperMinecart;
@ -20,83 +21,146 @@ import org.bukkit.entity.minecart.StorageMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.InventoryHolder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
* Handles inventory protection
* @author tastybento
*/
public class InventoryListener extends FlagListener {
public class InventoryListener extends FlagListener
{
/**
* Prevents players opening inventories
* @param event - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void onInventoryOpen(InventoryOpenEvent event)
{
InventoryHolder inventoryHolder = event.getInventory().getHolder();
if (inventoryHolder == null || !(event.getPlayer() instanceof Player player))
{
return;
}
if (inventoryHolder instanceof Animals)
{
// Prevent opening animal inventories.
this.checkIsland(event, player, event.getInventory().getLocation(), Flags.MOUNT_INVENTORY);
}
else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2) &&
inventoryHolder instanceof ChestBoat)
{
// Prevent opening chest inventories
this.checkIsland(event, player, event.getInventory().getLocation(), Flags.CHEST);
}
}
/**
* Prevents players picking items from inventories
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void onInventoryClick(InventoryClickEvent e) {
Player player = (Player)e.getWhoClicked();
public void onInventoryClick(InventoryClickEvent e)
{
Player player = (Player) e.getWhoClicked();
InventoryHolder inventoryHolder = e.getInventory().getHolder();
if (inventoryHolder == null || !(e.getWhoClicked() instanceof Player)) {
if (inventoryHolder == null || !(e.getWhoClicked() instanceof Player))
{
return;
}
if (inventoryHolder instanceof Animals) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.MOUNT_INVENTORY);
if (inventoryHolder instanceof Animals)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.MOUNT_INVENTORY);
}
else if (inventoryHolder instanceof Dispenser) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.DISPENSER);
else if (inventoryHolder instanceof Dispenser)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.DISPENSER);
}
else if (inventoryHolder instanceof Dropper) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.DROPPER);
else if (inventoryHolder instanceof Dropper)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.DROPPER);
}
else if (inventoryHolder instanceof Hopper
|| inventoryHolder instanceof HopperMinecart) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.HOPPER);
else if (inventoryHolder instanceof Hopper || inventoryHolder instanceof HopperMinecart)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.HOPPER);
}
else if (inventoryHolder instanceof Furnace) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.FURNACE);
else if (inventoryHolder instanceof Furnace)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.FURNACE);
}
else if (inventoryHolder instanceof BrewingStand) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.BREWING);
else if (inventoryHolder instanceof BrewingStand)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BREWING);
}
else if (inventoryHolder instanceof Beacon) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.BEACON);
else if (inventoryHolder instanceof Beacon)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BEACON);
}
else if (inventoryHolder instanceof NPC) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING);
else if (inventoryHolder instanceof NPC)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING);
}
else if (inventoryHolder instanceof Barrel) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL);
else if (inventoryHolder instanceof Barrel)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL);
}
else if (inventoryHolder instanceof ShulkerBox) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX);
else if (inventoryHolder instanceof ShulkerBox)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX);
}
else if (inventoryHolder instanceof Chest c) {
checkInvHolder(c.getLocation(), e, player);
else if (inventoryHolder instanceof Chest c)
{
this.checkInvHolder(c.getLocation(), e, player);
}
else if (inventoryHolder instanceof DoubleChest dc) {
checkInvHolder(dc.getLocation(), e, player);
else if (inventoryHolder instanceof DoubleChest dc)
{
this.checkInvHolder(dc.getLocation(), e, player);
}
else if (inventoryHolder instanceof StorageMinecart) {
checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST);
else if (inventoryHolder instanceof StorageMinecart)
{
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST);
}
else if (!(inventoryHolder instanceof Player)) {
else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, ServerCompatibility.ServerVersion.V1_18_1, ServerCompatibility.ServerVersion.V1_18_2) &&
inventoryHolder instanceof ChestBoat)
{
// TODO: 1.19 added chest boat. Remove compatibility check when 1.18 is dropped.
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST);
}
else if (!(inventoryHolder instanceof Player))
{
// All other containers
checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER);
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER);
}
}
private void checkInvHolder(Location l, InventoryClickEvent e, Player player) {
if (l.getBlock().getType().equals(Material.TRAPPED_CHEST)) {
checkIsland(e, player, l, Flags.TRAPPED_CHEST);
} else {
checkIsland(e, player, l, Flags.CHEST);
/**
* This method runs check based on clicked chest type.
* @param l location of chest.
* @param e click event.
* @param player player who clicked.
*/
private void checkInvHolder(Location l, InventoryClickEvent e, Player player)
{
if (l.getBlock().getType().equals(Material.TRAPPED_CHEST))
{
this.checkIsland(e, player, l, Flags.TRAPPED_CHEST);
}
else
{
this.checkIsland(e, player, l, Flags.CHEST);
}
}
}

View File

@ -104,22 +104,31 @@ public class LockAndBanListener extends FlagListener {
* @param loc - location to check
* @return CheckResult LOCKED, BANNED or OPEN. If an island is locked, that will take priority over banned
*/
private CheckResult check(@NonNull Player player, Location loc) {
private CheckResult check(@NonNull Player player, Location loc)
{
// Ops or NPC's are allowed everywhere
if (player.isOp() || player.hasMetadata("NPC")) {
if (player.isOp() || player.hasMetadata("NPC"))
{
return CheckResult.OPEN;
}
// See if the island is locked to non-members or player is banned
return getIslands().getProtectedIslandAt(loc)
.map(is -> {
if (is.isBanned(player.getUniqueId())) {
return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypassban") ? CheckResult.OPEN : CheckResult.BANNED;
}
if (!is.isAllowed(User.getInstance(player), Flags.LOCK)) {
return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypasslock") ? CheckResult.OPEN : CheckResult.LOCKED;
}
return CheckResult.OPEN;
}).orElse(CheckResult.OPEN);
return this.getIslands().getProtectedIslandAt(loc).
map(is ->
{
if (is.isBanned(player.getUniqueId()))
{
return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypassban") ?
CheckResult.OPEN : CheckResult.BANNED;
}
if (!is.isAllowed(User.getInstance(player), Flags.LOCK))
{
return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypasslock") ?
CheckResult.OPEN : CheckResult.LOCKED;
}
return CheckResult.OPEN;
}).
orElse(CheckResult.OPEN);
}
/**
@ -128,19 +137,17 @@ public class LockAndBanListener extends FlagListener {
* @param loc - location to check
* @return true if banned
*/
private CheckResult checkAndNotify(@NonNull Player player, Location loc) {
CheckResult r = check(player,loc);
switch (r) {
case BANNED:
User.getInstance(player).notify("commands.island.ban.you-are-banned");
break;
case LOCKED:
User.getInstance(player).notify("protection.locked");
break;
default:
break;
private CheckResult checkAndNotify(@NonNull Player player, Location loc)
{
CheckResult result = this.check(player, loc);
switch (result)
{
case BANNED -> User.getInstance(player).notify("commands.island.ban.you-are-banned");
case LOCKED -> User.getInstance(player).notify("protection.locked");
}
return r;
return result;
}
/**

View File

@ -13,67 +13,66 @@ import org.bukkit.event.player.PlayerInteractEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
/**
* @author tastybento
*
*/
public class PhysicalInteractionListener extends FlagListener {
public class PhysicalInteractionListener extends FlagListener
{
/**
* Handle physical interaction with blocks
* Crop trample, pressure plates, triggering redstone, tripwires
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent e) {
if (!e.getAction().equals(Action.PHYSICAL)) {
public void onPlayerInteract(PlayerInteractEvent e)
{
if (!e.getAction().equals(Action.PHYSICAL) || e.getClickedBlock() == null)
{
return;
}
if (isPressurePlate(e.getClickedBlock().getType())) {
if (Tag.PRESSURE_PLATES.isTagged(e.getClickedBlock().getType()))
{
// Pressure plates
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PRESSURE_PLATE);
this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PRESSURE_PLATE);
return;
}
switch (e.getClickedBlock().getType()) {
case FARMLAND:
// Crop trample
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.CROP_TRAMPLE);
break;
case TURTLE_EGG:
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.TURTLE_EGGS);
break;
default:
break;
switch (e.getClickedBlock().getType())
{
case FARMLAND -> this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.CROP_TRAMPLE);
case TURTLE_EGG -> this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.TURTLE_EGGS);
}
}
/**
* Protects buttons and plates from being activated by projectiles
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onProjectileHit(EntityInteractEvent e) {
if (!(e.getEntity() instanceof Projectile p)) {
public void onProjectileHit(EntityInteractEvent e)
{
if (!(e.getEntity() instanceof Projectile p))
{
return;
}
if (p.getShooter() instanceof Player) {
if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType())) {
checkIsland(e, (Player)p.getShooter(), e.getBlock().getLocation(), Flags.BUTTON);
if (p.getShooter() instanceof Player)
{
if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType()))
{
this.checkIsland(e, (Player) p.getShooter(), e.getBlock().getLocation(), Flags.BUTTON);
return;
}
if (isPressurePlate(e.getBlock().getType())) {
if (Tag.PRESSURE_PLATES.isTagged(e.getBlock().getType()))
{
// Pressure plates
checkIsland(e, (Player)p.getShooter(), e.getBlock().getLocation(), Flags.PRESSURE_PLATE);
this.checkIsland(e, (Player) p.getShooter(), e.getBlock().getLocation(), Flags.PRESSURE_PLATE);
}
}
}
private boolean isPressurePlate(Material material) {
return switch (material) {
case STONE_PRESSURE_PLATE, POLISHED_BLACKSTONE_PRESSURE_PLATE, ACACIA_PRESSURE_PLATE, BIRCH_PRESSURE_PLATE, CRIMSON_PRESSURE_PLATE, DARK_OAK_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE, JUNGLE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, OAK_PRESSURE_PLATE, SPRUCE_PRESSURE_PLATE, WARPED_PRESSURE_PLATE -> true;
default -> false;
};
}
}

View File

@ -18,95 +18,126 @@ import world.bentobox.bentobox.lists.Flags;
/**
* @author tastybento
*/
public class PlaceBlocksListener extends FlagListener {
public class PlaceBlocksListener extends FlagListener
{
/**
* Check blocks being placed in general
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBlockPlace(final BlockPlaceEvent e) {
if (e.getBlock().getType().equals(Material.FIRE)
|| e.getItemInHand() == null // Note that this should never happen officially, but it's possible for other plugins to cause it to happen
|| e.getItemInHand().getType().equals(Material.WRITABLE_BOOK)
|| e.getItemInHand().getType().equals(Material.WRITTEN_BOOK)) {
public void onBlockPlace(final BlockPlaceEvent e)
{
if (e.getBlock().getType().equals(Material.FIRE) ||
e.getItemInHand() == null || // Note that this should never happen officially, but it's possible for other plugins to cause it to happen
e.getItemInHand().getType().equals(Material.WRITABLE_BOOK) ||
e.getItemInHand().getType().equals(Material.WRITTEN_BOOK))
{
// Books can only be placed on lecterns and as such are protected by the LECTERN flag.
return;
}
checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS);
this.checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS);
}
/**
* Check for paintings and other hanging placements
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onHangingPlace(final HangingPlaceEvent e) {
checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS);
public void onHangingPlace(final HangingPlaceEvent e)
{
this.checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS);
}
/**
* Handles placing items into ItemFrames
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerHitItemFrame(PlayerInteractEntityEvent e) {
if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME)) {
if (!checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.PLACE_BLOCKS)) return;
checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.ITEM_FRAME);
public void onPlayerHitItemFrame(PlayerInteractEntityEvent e)
{
if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME) ||
e.getRightClicked().getType().equals(EntityType.GLOW_ITEM_FRAME))
{
if (!this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.PLACE_BLOCKS))
{
return;
}
this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.ITEM_FRAME);
}
}
/**
* Handle placing of fireworks, item frames, mine carts, end crystals, chests and boats on land
* The doors and chests are related to an exploit.
* Handle placing of fireworks, item frames, mine carts, end crystals, chests and boats on land The doors and chests
* are related to an exploit.
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerInteract(final PlayerInteractEvent e) {
if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) {
public void onPlayerInteract(final PlayerInteractEvent e)
{
if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null)
{
return;
}
switch (e.getClickedBlock().getType()) {
case FIREWORK_ROCKET:
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS);
return;
case RAIL:
case POWERED_RAIL:
case DETECTOR_RAIL:
case ACTIVATOR_RAIL:
if ((e.getMaterial() == Material.MINECART || e.getMaterial() == Material.CHEST_MINECART || e.getMaterial() == Material.HOPPER_MINECART
|| e.getMaterial() == Material.TNT_MINECART || e.getMaterial() == Material.FURNACE_MINECART)) {
checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.MINECART);
switch (e.getClickedBlock().getType())
{
case FIREWORK_ROCKET ->
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS);
}
return;
default:
// Check in-hand items
if (e.getMaterial().equals(Material.FIREWORK_ROCKET)
|| e.getMaterial().equals(Material.ARMOR_STAND)
|| e.getMaterial().equals(Material.END_CRYSTAL)
|| e.getMaterial().equals(Material.ITEM_FRAME)
//|| Tag.DOORS.isTagged(e.getMaterial())
|| e.getMaterial().equals(Material.CHEST)
|| e.getMaterial().equals(Material.TRAPPED_CHEST)) {
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PLACE_BLOCKS);
case RAIL, POWERED_RAIL, DETECTOR_RAIL, ACTIVATOR_RAIL ->
{
if (e.getMaterial() == Material.MINECART ||
e.getMaterial() == Material.CHEST_MINECART ||
e.getMaterial() == Material.HOPPER_MINECART ||
e.getMaterial() == Material.TNT_MINECART ||
e.getMaterial() == Material.FURNACE_MINECART)
{
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.MINECART);
}
}
else if (e.getMaterial().name().contains("BOAT")) {
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.BOAT);
default ->
{
// Check in-hand items
if (e.getMaterial() == Material.FIREWORK_ROCKET ||
e.getMaterial() == Material.ARMOR_STAND ||
e.getMaterial() == Material.END_CRYSTAL ||
e.getMaterial() == Material.ITEM_FRAME ||
e.getMaterial() == Material.GLOW_ITEM_FRAME ||
e.getMaterial() == Material.CHEST ||
e.getMaterial() == Material.TRAPPED_CHEST)
{
this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PLACE_BLOCKS);
}
else if (e.getMaterial().name().contains("BOAT"))
{
this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.BOAT);
}
}
}
}
/**
* Handles Frost Walking on visitor's islands. This creates ice blocks, which is like placing blocks
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void onBlockForm(EntityBlockFormEvent e) {
if (e.getNewState().getType().equals(Material.FROSTED_ICE) && e.getEntity() instanceof Player) {
checkIsland(e, (Player)e.getEntity(), e.getBlock().getLocation(), Flags.FROST_WALKER);
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBlockForm(EntityBlockFormEvent e)
{
if (e.getNewState().getType().equals(Material.FROSTED_ICE) && e.getEntity() instanceof Player)
{
this.checkIsland(e, (Player) e.getEntity(), e.getBlock().getLocation(), Flags.FROST_WALKER);
}
}
}

View File

@ -0,0 +1,53 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockReceiveGameEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
* This method prevents sculk sensor from activation based on protection settings.
*/
public class SculkSensorListener extends FlagListener
{
/**
* This listener detects if a visitor activates sculk sensor, and block it, if required.
* @param event Sculk activation event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onSculkSensor(BlockReceiveGameEvent event)
{
if (!this.getIWM().inWorld(event.getBlock().getWorld()))
{
return;
}
if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2))
{
// TODO: 1.18 compatibility exit
return;
}
if (event.getBlock().getType() == Material.SCULK_SENSOR &&
event.getEntity() != null &&
event.getEntity() instanceof Player player)
{
this.checkIsland(event, player, event.getBlock().getLocation(), Flags.SCULK_SENSOR, true);
}
}
}

View File

@ -0,0 +1,53 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.flags.protection;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockReceiveGameEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
* This method prevents sculk shrieker from activation based on protection settings.
*/
public class SculkShriekerListener extends FlagListener
{
/**
* This listener detects if a visitor activates sculk sensor, and block it, if required.
* @param event Sculk activation event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onSculkShrieker(BlockReceiveGameEvent event)
{
if (!this.getIWM().inWorld(event.getBlock().getWorld()))
{
return;
}
if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2))
{
// TODO: 1.18 compatibility exit
return;
}
if (event.getBlock().getType() == Material.SCULK_SHRIEKER &&
event.getEntity() != null &&
event.getEntity() instanceof Player player)
{
this.checkIsland(event, player, event.getBlock().getLocation(), Flags.SCULK_SHRIEKER, true);
}
}
}

View File

@ -3,11 +3,13 @@ package world.bentobox.bentobox.listeners.flags.settings;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.PufferFish;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.raid.RaidFinishEvent;
import org.bukkit.event.raid.RaidTriggerEvent;
import org.bukkit.potion.PotionEffectType;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.FlagListener;
@ -19,68 +21,136 @@ import world.bentobox.bentobox.util.Util;
* Handles natural mob spawning.
* @author tastybento
*/
public class MobSpawnListener extends FlagListener {
public class MobSpawnListener extends FlagListener
{
/**
* Prevents mobs spawning naturally
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onMobSpawnEvent(CreatureSpawnEvent e)
{
this.onMobSpawn(e);
}
/**
* This prevents to start a raid if mob spawning rules prevents it.
* @param event RaidTriggerEvent
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onRaidStartEvent(RaidTriggerEvent event)
{
// If not in the right world exit immediately.
if (!this.getIWM().inWorld(event.getWorld()))
{
return;
}
Optional<Island> island = getIslands().getIslandAt(event.getPlayer().getLocation());
if (island.map(i -> !i.isAllowed(Flags.MONSTER_NATURAL_SPAWN)).orElseGet(
() -> !Flags.MONSTER_NATURAL_SPAWN.isSetForWorld(event.getWorld())))
{
// Monster spawning is disabled on island or world. Cancel the raid.
event.setCancelled(true);
}
}
/**
* This removes HERO_OF_THE_VILLAGE from players that cheated victory.
* @param event RaidFinishEvent
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onRaidFinishEvent(RaidFinishEvent event)
{
// If not in the right world exit immediately.
if (!this.getIWM().inWorld(event.getWorld()))
{
return;
}
Optional<Island> island = getIslands().getIslandAt(event.getRaid().getLocation());
if (island.map(i -> !i.isAllowed(Flags.MONSTER_NATURAL_SPAWN)).orElseGet(
() -> !Flags.MONSTER_NATURAL_SPAWN.isSetForWorld(event.getWorld())))
{
// CHEATERS. PUNISH THEM.
event.getWinners().forEach(player ->
{
if (player.isOnline())
{
player.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE);
}
});
}
}
/**
* Prevents mobs spawning naturally
*
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onMobSpawnEvent(CreatureSpawnEvent e) {
onMobSpawn(e);
}
/**
* Prevents mobs spawning naturally
*
* @param e - event
* @return true if cancelled
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
void onMobSpawn(CreatureSpawnEvent e) {
void onMobSpawn(CreatureSpawnEvent e)
{
// If not in the right world, or spawning is not natural return
if (!getIWM().inWorld(e.getEntity().getLocation())) {
if (!this.getIWM().inWorld(e.getEntity().getLocation()))
{
return;
}
switch (e.getSpawnReason()) {
// Natural
case DEFAULT:
case DROWNED:
case JOCKEY:
case LIGHTNING:
case MOUNT:
case NATURAL:
case NETHER_PORTAL:
case OCELOT_BABY:
case PATROL:
case RAID:
case REINFORCEMENTS:
case SILVERFISH_BLOCK:
//case SLIME_SPLIT: messes with slimes from spawners, slime must have previously existed to create another
case TRAP:
case VILLAGE_DEFENSE:
case VILLAGE_INVASION:
boolean cancelNatural = shouldCancel(e.getEntity(), e.getLocation(), Flags.ANIMAL_NATURAL_SPAWN, Flags.MONSTER_NATURAL_SPAWN);
e.setCancelled(cancelNatural);
return;
switch (e.getSpawnReason())
{
// Natural
case DEFAULT, DROWNED, JOCKEY, LIGHTNING, MOUNT, NATURAL, NETHER_PORTAL, OCELOT_BABY, PATROL,
RAID, REINFORCEMENTS, SILVERFISH_BLOCK, TRAP, VILLAGE_DEFENSE, VILLAGE_INVASION ->
{
boolean cancelNatural = this.shouldCancel(e.getEntity(),
e.getLocation(),
Flags.ANIMAL_NATURAL_SPAWN,
Flags.MONSTER_NATURAL_SPAWN);
e.setCancelled(cancelNatural);
}
// Spawners
case SPAWNER:
boolean cancelSpawners = shouldCancel(e.getEntity(), e.getLocation(), Flags.ANIMAL_SPAWNERS_SPAWN, Flags.MONSTER_SPAWNERS_SPAWN);
e.setCancelled(cancelSpawners);
return;
default:
return;
case SPAWNER ->
{
boolean cancelSpawners = this.shouldCancel(e.getEntity(),
e.getLocation(),
Flags.ANIMAL_SPAWNERS_SPAWN,
Flags.MONSTER_SPAWNERS_SPAWN);
e.setCancelled(cancelSpawners);
}
}
}
private boolean shouldCancel(Entity entity, Location loc, Flag animalSpawnFlag, Flag monsterSpawnFlag) {
/**
* This method checks if entity should be cancelled from spawning in given location base on flag values.
* @param entity Entity that is checked.
* @param loc location where entity is spawned.
* @param animalSpawnFlag Animal Spawn Flag.
* @param monsterSpawnFlag Monster Spawn Flag.
* @return {@code true} if flag prevents entity to spawn, {@code false} otherwise.
*/
private boolean shouldCancel(Entity entity, Location loc, Flag animalSpawnFlag, Flag monsterSpawnFlag)
{
Optional<Island> island = getIslands().getIslandAt(loc);
if (Util.isHostileEntity(entity) && !(entity instanceof PufferFish)) {
return island.map(i -> !i.isAllowed(monsterSpawnFlag)).orElseGet(() -> !monsterSpawnFlag.isSetForWorld(entity.getWorld()));
} else if (Util.isPassiveEntity(entity) || entity instanceof PufferFish) {
return island.map(i -> !i.isAllowed(animalSpawnFlag)).orElseGet(() -> !animalSpawnFlag.isSetForWorld(entity.getWorld()));
}
return false;
}
}
if (Util.isHostileEntity(entity) && !(entity instanceof PufferFish))
{
return island.map(i -> !i.isAllowed(monsterSpawnFlag)).
orElseGet(() -> !monsterSpawnFlag.isSetForWorld(entity.getWorld()));
}
else if (Util.isPassiveEntity(entity) || entity instanceof PufferFish)
{
return island.map(i -> !i.isAllowed(animalSpawnFlag)).
orElseGet(() -> !animalSpawnFlag.isSetForWorld(entity.getWorld()));
}
else
{
return false;
}
}
}

View File

@ -1,6 +1,7 @@
package world.bentobox.bentobox.listeners.flags.worldsettings;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityExplodeEvent;
@ -18,12 +19,13 @@ public class ChestDamageListener extends FlagListener {
* @param e - event
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onExplosion(final EntityExplodeEvent e) {
if (getIWM().inWorld(e.getLocation()) && !Flags.CHEST_DAMAGE.isSetForWorld(e.getLocation().getWorld())) {
e.blockList().removeIf(b -> b.getType().equals(Material.CHEST)
|| b.getType().equals(Material.TRAPPED_CHEST)
|| b.getType().name().contains("SHULKER_BOX")
);
public void onExplosion(final EntityExplodeEvent e)
{
if (getIWM().inWorld(e.getLocation()) && !Flags.CHEST_DAMAGE.isSetForWorld(e.getLocation().getWorld()))
{
e.blockList().removeIf(b -> b.getType().equals(Material.CHEST) ||
b.getType().equals(Material.TRAPPED_CHEST) ||
Tag.SHULKER_BOXES.isTagged(b.getType()));
}
}
}

View File

@ -35,6 +35,28 @@ public class CoarseDirtTillingListener extends FlagListener {
}
}
/**
* Protect Coarse dirt and podzol from being made as dirt path block.
* @param e PlayerInteractEvent
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onDirtPathCreation(PlayerInteractEvent e)
{
if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) &&
e.getItem() != null &&
this.getIWM().inWorld(e.getClickedBlock().getWorld()) &&
(e.getClickedBlock().getType().equals(Material.COARSE_DIRT) || e.getClickedBlock().getType().equals(Material.PODZOL)) &&
e.getItem().getType().name().endsWith("_SHOVEL") &&
!Flags.COARSE_DIRT_TILLING.isSetForWorld(e.getClickedBlock().getWorld()))
{
e.setCancelled(true);
User user = User.getInstance(e.getPlayer());
user.notify("protection.protected", TextVariables.DESCRIPTION, user.getTranslation(Flags.COARSE_DIRT_TILLING.getHintReference()));
}
}
/**
* If podzol is mined when coarse dirt tilling is not allowed, then it'll just drop podzol and not dirt
* This prevents an exploit where growing big spruce trees can turn gravel into podzol.

View File

@ -12,6 +12,8 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
import org.bukkit.event.inventory.ClickType;
import world.bentobox.bentobox.BentoBox;
@ -157,5 +159,28 @@ public class InvincibleVisitorsListener extends FlagListener implements ClickHan
}
/**
* This listener cancels entity targeting a player if he is a visitor, and visitors are immune to entity damage.
* @param e EntityTargetLivingEntityEvent
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onVisitorTargeting(EntityTargetLivingEntityEvent e)
{
World world = e.getEntity().getWorld();
if (!(e.getTarget() instanceof Player p) ||
!this.getIWM().inWorld(world) ||
e.getTarget().hasMetadata("NPC") ||
this.getIslands().userIsOnIsland(world, User.getInstance(e.getTarget())) ||
this.PVPAllowed(p.getLocation()) ||
e.getReason() == EntityTargetEvent.TargetReason.TARGET_DIED ||
!this.getIWM().getIvSettings(world).contains(DamageCause.ENTITY_ATTACK.name()))
{
return;
}
// Cancel targeting event.
e.setCancelled(true);
}
}

View File

@ -2,9 +2,12 @@ package world.bentobox.bentobox.listeners.flags.worldsettings;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockFromToEvent;
import world.bentobox.bentobox.api.flags.FlagListener;
@ -43,4 +46,38 @@ public class LiquidsFlowingOutListener extends FlagListener {
}
}
/**
* Prevents players from dispensing water, lava and powdered snow from dispenser outside island
* if Flags.LIQUIDS_FLOWING_OUT is disabled.
* @param event BlockDispenseEvent
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onDispenserLiquid(BlockDispenseEvent event)
{
Location from = event.getBlock().getLocation();
if (!this.getIWM().inWorld(from) || Flags.LIQUIDS_FLOWING_OUT.isSetForWorld(from.getWorld())) {
// We do not want to run any check if this is not the right world or if it is allowed.
return;
}
Location to = event.getVelocity().toLocation(event.getBlock().getWorld());
if (!event.getItem().getType().equals(Material.WATER_BUCKET) &&
!event.getItem().getType().equals(Material.LAVA_BUCKET) &&
!event.getItem().getType().equals(Material.POWDER_SNOW_BUCKET))
{
return;
}
// Only prevent if it is flowing into the area between islands or into another island.
Optional<Island> fromIsland = this.getIslandsManager().getProtectedIslandAt(from);
Optional<Island> toIsland = this.getIslandsManager().getProtectedIslandAt(to);
if (toIsland.isEmpty() || (fromIsland.isPresent() && !fromIsland.equals(toIsland)))
{
event.setCancelled(true);
}
}
}

View File

@ -0,0 +1,61 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.flags.worldsettings;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.raid.RaidTriggerEvent;
import java.util.Optional;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
/**
* This listener checks for island visitors that want to start a new raid.
*/
public class VisitorsStartingRaidListener extends FlagListener
{
/**
* This method process raid allowance from visitors.
* @param event RaidTriggerEvent
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onRaidTrigger(RaidTriggerEvent event)
{
World world = Util.getWorld(event.getWorld());
if (!this.getIWM().inWorld(world) || Flags.VISITOR_TRIGGER_RAID.isSetForWorld(world))
{
// If the player triggers raid non-protected world or VISITOR_TRIGGER_RAID is disabled then do nothing.
this.report(User.getInstance(event.getPlayer()),
event,
event.getPlayer().getLocation(),
Flags.VISITOR_TRIGGER_RAID,
Why.SETTING_ALLOWED_IN_WORLD);
return;
}
Optional<Island> island = this.getIslands().getProtectedIslandAt(event.getPlayer().getLocation());
if (island.isPresent() && !island.get().getMemberSet().contains(event.getPlayer().getUniqueId()))
{
event.setCancelled(true);
this.report(User.getInstance(event.getPlayer()),
event,
event.getPlayer().getLocation(),
Flags.VISITOR_TRIGGER_RAID,
Why.SETTING_NOT_ALLOWED_IN_WORLD);
}
}
}

View File

@ -0,0 +1,344 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.teleports;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import java.util.*;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
/**
* This abstract class contains all common methods for entity and player teleportation.
*/
public abstract class AbstractTeleportListener
{
/**
* Instance of Teleportation processor.
* @param bentoBox BentoBox plugin.
*/
AbstractTeleportListener(@NonNull BentoBox bentoBox)
{
this.plugin = bentoBox;
this.inPortal = new HashSet<>();
this.inTeleport = new HashSet<>();
this.teleportOrigin = new HashMap<>();
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* Get island at the given location
* @return optional island at given location
*/
protected Optional<Island> getIsland(Location location)
{
return this.plugin.getIslandsManager().getProtectedIslandAt(location);
}
/**
* Get island for given player at the given world.
* @return optional island at given world.
*/
protected Optional<Island> getIsland(World world, Player player)
{
return Optional.ofNullable(this.plugin.getIslandsManager().getIsland(world, player.getUniqueId()));
}
/**
* Check if vanilla portals should be used
*
* @param world - game mode world
* @param environment - environment
* @return true or false
*/
protected boolean isMakePortals(World world, World.Environment environment)
{
return this.plugin.getIWM().getAddon(world).
map(gameMode -> this.isMakePortals(gameMode, environment)).
orElse(false);
}
/**
* Check if vanilla portals should be used
*
* @param gameMode - game mode
* @param environment - environment
* @return true or false
*/
protected boolean isMakePortals(GameModeAddon gameMode, World.Environment environment)
{
return switch (environment) {
case NETHER -> gameMode.getWorldSettings().isMakeNetherPortals();
case THE_END -> gameMode.getWorldSettings().isMakeEndPortals();
default -> false;
};
}
/**
* Check if nether or end are generated
*
* @param overWorld - game world
* @param environment - environment
* @return true or false
*/
protected boolean isAllowedInConfig(World overWorld, World.Environment environment)
{
return switch (environment) {
case NETHER -> this.plugin.getIWM().isNetherGenerate(overWorld);
case THE_END -> this.plugin.getIWM().isEndGenerate(overWorld);
default -> true;
};
}
/**
* Check if the default nether or end are allowed by the server settings
*
* @param environment - environment
* @return true or false
*/
protected boolean isAllowedOnServer(World.Environment environment)
{
return switch (environment) {
case NETHER -> Bukkit.getAllowNether();
case THE_END -> Bukkit.getAllowEnd();
default -> true;
};
}
/**
* Check if nether or end islands are generated
*
* @param overWorld - over world
* @param environment - environment
* @return true or false
*/
protected boolean isIslandWorld(World overWorld, World.Environment environment)
{
return switch (environment) {
case NETHER -> this.plugin.getIWM().isNetherIslands(overWorld);
case THE_END -> this.plugin.getIWM().isEndIslands(overWorld);
default -> true;
};
}
/**
* Get the nether or end world
*
* @param overWorld - over world
* @param environment - environment
* @return nether or end world
*/
protected World getNetherEndWorld(World overWorld, World.Environment environment)
{
return switch (environment) {
case NETHER -> this.plugin.getIWM().getNetherWorld(overWorld);
case THE_END -> this.plugin.getIWM().getEndWorld(overWorld);
default -> Util.getWorld(overWorld);
};
}
/**
* Check if the island has a nether or end island already
*
* @param island - island
* @param environment - environment
* @return true or false
*/
protected boolean hasPartnerIsland(Island island, World.Environment environment)
{
return switch (environment) {
case NETHER -> island.hasNetherIsland();
case THE_END -> island.hasEndIsland();
default -> true;
};
}
/**
* This method calculates the maximal search area for portal.
* @param location Location from which search should happen.
* @param island Island that contains the search point.
* @return Search range for portal.
*/
protected int calculateSearchRadius(Location location, Island island)
{
int diff;
if (island.onIsland(location))
{
// Find max x or max z
int x = Math.abs(island.getProtectionCenter().getBlockX() - location.getBlockX());
int z = Math.abs(island.getProtectionCenter().getBlockZ() - location.getBlockZ());
diff = Math.min(this.plugin.getSettings().getSafeSpotSearchRange(),
island.getProtectionRange() - Math.max(x, z));
}
else
{
diff = this.plugin.getSettings().getSafeSpotSearchRange();
}
return diff;
}
/**
* This method calculates location for portal.
* @param fromLocation Location from which teleportation happens.
* @param fromWorld World from which teleportation happens.
* @param toWorld The target world.
* @param environment Portal variant.
* @param canCreatePortal Indicates if portal should be created or not.
* @return Location for new portal.
*/
protected Location calculateLocation(Location fromLocation,
World fromWorld,
World toWorld,
World.Environment environment,
boolean canCreatePortal)
{
// Null check - not that useful
if (fromWorld == null || toWorld == null)
{
return null;
}
Location toLocation = fromLocation.toVector().toLocation(toWorld);
if (!this.isMakePortals(fromWorld, environment))
{
toLocation = this.getIsland(fromLocation).
map(island -> island.getSpawnPoint(toWorld.getEnvironment())).
orElse(toLocation);
}
// Limit Y to the min/max world height.
toLocation.setY(Math.max(Math.min(toLocation.getY(), toWorld.getMaxHeight()), toWorld.getMinHeight()));
if (!canCreatePortal)
{
// Legacy portaling
return toLocation;
}
// Make portals
// For anywhere other than the end - it is the player's location that is used
if (!environment.equals(World.Environment.THE_END))
{
return toLocation;
}
// If the-end then we want the platform to always be generated in the same place no matter where
// they enter the portal
final int x = fromLocation.getBlockX();
final int z = fromLocation.getBlockZ();
final int y = fromLocation.getBlockY();
int i = x;
int j = z;
int k = y;
// If the from is not a portal, then we have to find it
if (!fromLocation.getBlock().getType().equals(Material.END_PORTAL))
{
// Find the portal - due to speed, it is possible that the player will be below or above the portal
for (k = toWorld.getMinHeight(); (k < fromWorld.getMaxHeight()) &&
!fromWorld.getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++);
}
// Find the maximum x and z corner
for (; (i < x + 5) && fromWorld.getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++) ;
for (; (j < z + 5) && fromWorld.getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++) ;
// Mojang end platform generation is:
// AIR
// AIR
// OBSIDIAN
// and player is placed on second air block above obsidian.
// If Y coordinate is below 2, then obsidian platform is not generated and player falls in void.
return new Location(toWorld, i, Math.max(toWorld.getMinHeight() + 2, k), j);
}
/**
* This method returns spawn location for given world.
* @param world World which spawn point must be returned.
* @return Spawn location for world or null.
*/
@Nullable
protected Location getSpawnLocation(World world)
{
return this.plugin.getIslandsManager().getSpawn(world).map(island ->
island.getSpawnPoint(World.Environment.NORMAL) == null ?
island.getCenter() :
island.getSpawnPoint(World.Environment.NORMAL)).
orElse(this.plugin.getIslands().isSafeLocation(world.getSpawnLocation()) ?
world.getSpawnLocation() : null);
}
/**
* This method returns if missing islands should be generated uppon teleportation.
* Can happen only in non-custom generators.
* @param overWorld OverWorld
* @return {@code true} if missing islands must be pasted, {@code false} otherwise.
*/
protected boolean isPastingMissingIslands(World overWorld)
{
return this.plugin.getIWM().isPasteMissingIslands(overWorld) &&
!this.plugin.getIWM().isUseOwnGenerator(overWorld);
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* BentoBox plugin instance.
*/
@NonNull
protected final BentoBox plugin;
/**
* Set of entities that currently is inside portal.
*/
protected final Set<UUID> inPortal;
/**
* Map that links entities origin of teleportation. Used for respawning.
*/
protected final Map<UUID, World> teleportOrigin;
/**
* Set of entities that currently is in teleportation.
*/
protected final Set<UUID> inTeleport;
}

View File

@ -0,0 +1,408 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.teleports;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.event.entity.EntityPortalExitEvent;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import java.util.UUID;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport;
/**
* This class handles entity teleportation between dimensions.
*
* @author BONNe
*/
public class EntityTeleportListener extends AbstractTeleportListener implements Listener
{
/**
* Instance of Teleportation processor.
*
* @param bentoBox BentoBox plugin.
*/
public EntityTeleportListener(@NonNull BentoBox bentoBox)
{
super(bentoBox);
}
/**
* This listener checks entity portal events and triggers appropriate methods to transfer
* entities to the correct location in other dimension.
*
* This event is triggered when entity is about to being teleported because of contact with the
* nether portal or end gateway portal (exit portal triggers respawn).
*
* This event is not called if nether/end is disabled in server settings.
*
* @param event the entity portal event.
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onEntityPortal(EntityPortalEvent event)
{
World fromWorld = event.getFrom().getWorld();
World overWorld = Util.getWorld(fromWorld);
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld) || event.getTo() == null)
{
// Not a bentobox world.
return;
}
if (!Flags.ENTITY_PORTAL_TELEPORT.isSetForWorld(overWorld))
{
// Teleportation is disabled. Cancel event.
event.setCancelled(true);
}
// Trigger event processor.
this.portalProcess(event, event.getTo().getWorld().getEnvironment());
}
/**
* Fires the event if nether or end is disabled at the system level
*
* @param event - EntityPortalEnterEvent
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onEntityEnterPortal(EntityPortalEnterEvent event)
{
if (EntityType.PLAYER.equals(event.getEntity().getType()))
{
// This handles only non-players.
return;
}
Entity entity = event.getEntity();
Material type = event.getLocation().getBlock().getType();
UUID uuid = entity.getUniqueId();
if (this.inPortal.contains(uuid))
{
// Already in process.
return;
}
World fromWorld = event.getLocation().getWorld();
World overWorld = Util.getWorld(fromWorld);
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld))
{
// Not a bentobox world.
return;
}
if (!Flags.ENTITY_PORTAL_TELEPORT.isSetForWorld(overWorld))
{
// Teleportation is disabled. Cancel processing.
return;
}
this.inPortal.add(uuid);
// Add original world for respawning.
this.teleportOrigin.put(uuid, fromWorld);
// Entities are teleported instantly.
if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL))
{
if (fromWorld == overWorld)
{
this.portalProcess(
new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0),
World.Environment.NETHER);
}
else
{
this.portalProcess(
new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0),
World.Environment.NORMAL);
}
// Do not process anything else.
return;
}
// Entities are teleported instantly.
if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY)))
{
if (fromWorld == this.getNetherEndWorld(overWorld, World.Environment.THE_END))
{
this.portalProcess(
new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0),
World.Environment.NORMAL);
}
else
{
this.portalProcess(
new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0),
World.Environment.THE_END);
}
}
}
/**
* Remove inPortal flag only when entity exits the portal
*
* @param event entity move event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityExitPortal(EntityPortalExitEvent event)
{
if (!this.inPortal.contains(event.getEntity().getUniqueId()))
{
return;
}
this.inPortal.remove(event.getEntity().getUniqueId());
this.inTeleport.remove(event.getEntity().getUniqueId());
this.teleportOrigin.remove(event.getEntity().getUniqueId());
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* This method process entity teleportation to a correct dimension.
* @param event Event that triggers teleportation.
* @param environment Environment of the dimension where entity must appear.
*/
private void portalProcess(EntityPortalEvent event, World.Environment environment)
{
World fromWorld = event.getFrom().getWorld();
World overWorld = Util.getWorld(fromWorld);
if (fromWorld == null || overWorld == null)
{
// Missing worlds.
event.setCancelled(true);
return;
}
if (!this.isAllowedInConfig(overWorld, environment))
{
// World is disabled in config. Do not teleport player.
event.setCancelled(true);
return;
}
if (!this.isAllowedOnServer(environment))
{
// World is disabled in bukkit. Event is not triggered, but cancel by chance.
event.setCancelled(true);
}
if (this.inTeleport.contains(event.getEntity().getUniqueId()))
{
// Entity is already in teleportation.
return;
}
this.inTeleport.add(event.getEntity().getUniqueId());
// Get target world.
World toWorld;
if (environment.equals(World.Environment.NORMAL))
{
toWorld = overWorld;
}
else
{
toWorld = this.getNetherEndWorld(overWorld, environment);
}
if (!overWorld.equals(toWorld) && !this.isIslandWorld(overWorld, environment))
{
// This is not island world. Use standard nether or end world teleportation.
this.handleToStandardNetherOrEnd(event, overWorld, toWorld);
return;
}
if (!overWorld.equals(fromWorld) && !this.isIslandWorld(overWorld, environment))
{
// If entering a portal in the other world, teleport to a portal in overworld if
// there is one
this.handleFromStandardNetherOrEnd(event, overWorld, toWorld.getEnvironment());
return;
}
// Set the destination location
// If portals cannot be created, then destination is the spawn point, otherwise it's the vector
event.setTo(this.calculateLocation(event.getFrom(),
fromWorld,
toWorld,
environment,
this.isMakePortals(overWorld, environment)));
// Calculate search radius for portal
this.getIsland(event.getTo()).ifPresent(island ->
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
// Check if there is an island there or not
if (this.isPastingMissingIslands(overWorld) &&
this.isAllowedInConfig(overWorld, environment) &&
this.isIslandWorld(overWorld, environment) &&
this.getNetherEndWorld(overWorld, environment) != null &&
this.getIsland(event.getTo()).
filter(island -> !this.hasPartnerIsland(island, environment)).
map(island -> {
event.setCancelled(true);
return true;
}).
orElse(false))
{
// If there is no island, then processor already entity cannot be teleported before player
// visit that dimension.
return;
}
if (!event.isCancelled())
{
// Let the server teleport
return;
}
if (environment.equals(World.Environment.THE_END))
{
// Prevent death from hitting the ground while calculating location.
event.getEntity().setVelocity(new Vector(0,0,0));
event.getEntity().setFallDistance(0);
}
// If we do not generate portals, teleportation should happen manually with safe spot builder.
// Otherwise, we could end up with situations when player is placed in mid air, if teleportation
// is done instantly.
// Our safe spot task is triggered in next tick, however, end teleportation happens in the same tick.
// It is placed outside THE_END check, as technically it could happen with the nether portal too.
// If there is a portal to go to already, then the player will go there
Bukkit.getScheduler().runTask(this.plugin, () -> {
if (!event.getEntity().getWorld().equals(toWorld))
{
// Else manually teleport entity
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getEntity()).
location(event.getTo()).
portal().
successRunnable(() -> {
// Reset velocity just in case.
event.getEntity().setVelocity(new Vector(0,0,0));
event.getEntity().setFallDistance(0);
}).
build();
}
});
}
/**
* Handle teleport to standard nether or end
* @param event - EntityPortalEvent
* @param overWorld - over world
* @param toWorld - to world
*/
private void handleToStandardNetherOrEnd(EntityPortalEvent event, World overWorld, World toWorld)
{
Location spawnPoint = toWorld.getSpawnLocation();
// If going to the nether and nether portals are active then just teleport to approx location
if (World.Environment.NETHER.equals(toWorld.getEnvironment()) &&
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
{
spawnPoint = event.getFrom().toVector().toLocation(toWorld);
}
// If spawn is set as 0,63,0 in the End then move it to 100, 50 ,0.
if (World.Environment.THE_END.equals(toWorld.getEnvironment()) && spawnPoint.getBlockX() == 0 && spawnPoint.getBlockZ() == 0)
{
// Set to the default end spawn
spawnPoint = new Location(toWorld, 100, 50, 0);
toWorld.setSpawnLocation(100, 50, 0);
}
if (this.isAllowedOnServer(toWorld.getEnvironment()))
{
// To Standard Nether or end
event.setTo(spawnPoint);
}
else
{
// Teleport to standard nether or end
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getEntity()).
location(spawnPoint).
portal().
build();
}
}
/**
* Handle teleport from standard nether or end
* @param event - EntityPortalEvent
* @param overWorld - over world
* @param environment - to world environment
*/
private void handleFromStandardNetherOrEnd(EntityPortalEvent event, World overWorld, World.Environment environment)
{
if (World.Environment.NETHER.equals(environment) &&
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
{
// Set to location directly to the from location.
event.setTo(event.getFrom().toVector().toLocation(overWorld));
// Update portal search radius.
this.getIsland(event.getTo()).ifPresent(island ->
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
}
else
{
// Cannot be portal. Should recalculate position.
Location spawnLocation = this.getSpawnLocation(overWorld);
event.setTo(spawnLocation == null ?
event.getFrom().toVector().toLocation(overWorld) :
spawnLocation);
}
if (!this.isAllowedOnServer(environment))
{
// Custom portal handling.
event.setCancelled(true);
// Teleport to standard nether or end
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getEntity()).
location(event.getTo()).
portal().
build();
}
}
}

View File

@ -0,0 +1,501 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.listeners.teleports;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import java.util.Objects;
import java.util.UUID;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintPaster;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport;
/**
* This class handles player teleportation between dimensions.
*
* @author tastybento and BONNe
*/
public class PlayerTeleportListener extends AbstractTeleportListener implements Listener
{
/**
* Instantiates a new Portal teleportation listener.
*
* @param plugin the plugin
*/
public PlayerTeleportListener(@NonNull BentoBox plugin)
{
super(plugin);
}
// ---------------------------------------------------------------------
// Section: Listeners
// ---------------------------------------------------------------------
/**
* This listener checks player portal events and triggers appropriate methods to transfer
* players to the correct location in other dimension.
*
* This event is triggered when player is about to being teleported because of contact with the
* nether portal or end gateway portal (exit portal triggers respawn).
*
* This event is not called if nether/end is disabled in server settings.
*
* @param event the player portal event.
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerPortalEvent(PlayerPortalEvent event)
{
switch (event.getCause())
{
case NETHER_PORTAL -> this.portalProcess(event, World.Environment.NETHER);
case END_PORTAL, END_GATEWAY -> this.portalProcess(event, World.Environment.THE_END);
}
}
/**
* Fires the event if nether or end is disabled at the system level
*
* @param event - EntityPortalEnterEvent
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerPortal(EntityPortalEnterEvent event)
{
if (!EntityType.PLAYER.equals(event.getEntity().getType()))
{
// This handles only players.
return;
}
Entity entity = event.getEntity();
Material type = event.getLocation().getBlock().getType();
UUID uuid = entity.getUniqueId();
if (this.inPortal.contains(uuid) ||
!this.plugin.getIWM().inWorld(Util.getWorld(event.getLocation().getWorld())))
{
return;
}
this.inPortal.add(uuid);
// Add original world for respawning.
this.teleportOrigin.put(uuid, event.getLocation().getWorld());
if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL))
{
// Schedule a time
Bukkit.getScheduler().runTaskLater(this.plugin, () ->
{
// Check again if still in portal
if (this.inPortal.contains(uuid))
{
// Create new PlayerPortalEvent
PlayerPortalEvent en = new PlayerPortalEvent((Player) entity,
event.getLocation(),
null,
PlayerTeleportEvent.TeleportCause.NETHER_PORTAL,
0,
false,
0);
this.portalProcess(en, World.Environment.NETHER);
}
}, 40);
return;
}
// End portals are instant transfer
if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY)))
{
// Create new PlayerPortalEvent
PlayerPortalEvent en = new PlayerPortalEvent((Player) entity,
event.getLocation(),
null,
type.equals(Material.END_PORTAL) ? PlayerTeleportEvent.TeleportCause.END_PORTAL : PlayerTeleportEvent.TeleportCause.END_GATEWAY,
0,
false,
0);
this.portalProcess(en, World.Environment.THE_END);
}
}
/**
* Remove inPortal flag only when player exits the portal
*
* @param event player move event
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onExitPortal(PlayerMoveEvent event)
{
if (!this.inPortal.contains(event.getPlayer().getUniqueId()))
{
return;
}
if (event.getTo() != null && !event.getTo().getBlock().getType().equals(Material.NETHER_PORTAL))
{
// Player exits nether portal.
this.inPortal.remove(event.getPlayer().getUniqueId());
this.inTeleport.remove(event.getPlayer().getUniqueId());
this.teleportOrigin.remove(event.getPlayer().getUniqueId());
}
}
/**
* Player respawn event is triggered when player enters exit portal at the end.
* This will take over respawn mechanism and place player on island.
* @param event player respawn event
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerExitPortal(PlayerRespawnEvent event)
{
if (!this.teleportOrigin.containsKey(event.getPlayer().getUniqueId()))
{
// Player is already processed.
return;
}
World fromWorld = this.teleportOrigin.get(event.getPlayer().getUniqueId());
World overWorld = Util.getWorld(fromWorld);
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld))
{
// Not teleporting from/to bentobox worlds.
return;
}
this.getIsland(overWorld, event.getPlayer()).ifPresentOrElse(island -> {
if (!island.onIsland(event.getRespawnLocation()))
{
// If respawn location is outside island protection range, change location to the
// spawn in overworld or home location.
Location location = island.getSpawnPoint(World.Environment.NORMAL);
if (location == null)
{
// No spawn point. Rare thing. Well, use island protection center.
location = island.getProtectionCenter();
}
event.setRespawnLocation(location);
}
},
() -> {
// Player does not an island. Try to get spawn island, and if that fails, use world spawn point.
// If spawn point is not safe, do nothing. Let server handle it.
Location spawnLocation = this.getSpawnLocation(overWorld);
if (spawnLocation != null)
{
event.setRespawnLocation(spawnLocation);
}
});
}
// ---------------------------------------------------------------------
// Section: Processors
// ---------------------------------------------------------------------
/**
* This method process player teleportation to new dimension.
* @param event Event that triggers teleportation.
* @param environment Environment of portal type.
*/
private void portalProcess(PlayerPortalEvent event, World.Environment environment)
{
World fromWorld = event.getFrom().getWorld();
World overWorld = Util.getWorld(fromWorld);
if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld))
{
// Not teleporting from/to bentobox worlds.
return;
}
if (!this.isAllowedInConfig(overWorld, environment))
{
// World is disabled in config. Do not teleport player.
event.setCancelled(true);
return;
}
if (!this.isAllowedOnServer(environment))
{
// World is disabled in bukkit. Event is not triggered, but cancel by chance.
event.setCancelled(true);
}
if (this.inTeleport.contains(event.getPlayer().getUniqueId()))
{
// Player is already in teleportation.
return;
}
this.inTeleport.add(event.getPlayer().getUniqueId());
if (fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment))
{
// This is not island world. Use standard nether or end world teleportation.
this.handleToStandardNetherOrEnd(event, overWorld, environment);
return;
}
if (!fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment))
{
// If entering a portal in the other world, teleport to a portal in overworld if
// there is one
this.handleFromStandardNetherOrEnd(event, overWorld, environment);
return;
}
// To the nether/end or overworld.
World toWorld = !fromWorld.getEnvironment().equals(environment) ?
this.getNetherEndWorld(overWorld, environment) : overWorld;
// Set whether portals should be created or not
event.setCanCreatePortal(this.isMakePortals(overWorld, environment));
// Default 16 is will always end up placing portal as close to X/8 coordinate as possible.
// In most situations, 2 block value should be enough... I hope.
event.setCreationRadius(2);
// Set the destination location
// If portals cannot be created, then destination is the spawn point, otherwise it's the vector
event.setTo(this.calculateLocation(event.getFrom(), fromWorld, toWorld, environment, event.getCanCreatePortal()));
// Find the distance from edge of island's protection and set the search radius
this.getIsland(event.getTo()).ifPresent(island ->
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
// Check if there is an island there or not
if (this.isPastingMissingIslands(overWorld) &&
this.isAllowedInConfig(overWorld, environment) &&
this.isIslandWorld(overWorld, environment) &&
this.getNetherEndWorld(overWorld, environment) != null &&
this.getIsland(event.getTo()).
filter(island -> !this.hasPartnerIsland(island, environment)).
map(island -> {
event.setCancelled(true);
this.pasteNewIsland(event.getPlayer(), event.getTo(), island, environment);
return true;
}).
orElse(false))
{
// If there is no island, then processor already created island. Nothing to do more.
return;
}
if (!event.isCancelled() && event.getCanCreatePortal())
{
// Let the server teleport
return;
}
if (environment.equals(World.Environment.THE_END))
{
// Prevent death from hitting the ground while calculating location.
event.getPlayer().setVelocity(new Vector(0,0,0));
event.getPlayer().setFallDistance(0);
}
// If we do not generate portals, teleportation should happen manually with safe spot builder.
// Otherwise, we could end up with situations when player is placed in mid air, if teleportation
// is done instantly.
// Our safe spot task is triggered in next tick, however, end teleportation happens in the same tick.
// It is placed outside THE_END check, as technically it could happen with the nether portal too.
// If there is a portal to go to already, then the player will go there
Bukkit.getScheduler().runTask(this.plugin, () -> {
if (!event.getPlayer().getWorld().equals(toWorld))
{
// Else manually teleport entity
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getPlayer()).
location(event.getTo()).
portal().
successRunnable(() -> {
// Reset velocity just in case.
event.getPlayer().setVelocity(new Vector(0,0,0));
event.getPlayer().setFallDistance(0);
}).
build();
}
});
}
/**
* Handle teleport from or to standard nether or end
* @param event - PlayerPortalEvent
* @param overWorld - over world
* @param environment - environment involved
*/
private void handleToStandardNetherOrEnd(PlayerPortalEvent event,
World overWorld,
World.Environment environment)
{
World toWorld = Objects.requireNonNull(this.getNetherEndWorld(overWorld, environment));
Location spawnPoint = toWorld.getSpawnLocation();
// If going to the nether and nether portals are active then just teleport to approx location
if (environment.equals(World.Environment.NETHER) &&
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
{
spawnPoint = event.getFrom().toVector().toLocation(toWorld);
}
// If spawn is set as 0,63,0 in the End then move it to 100, 50 ,0.
if (environment.equals(World.Environment.THE_END) && spawnPoint.getBlockX() == 0 && spawnPoint.getBlockZ() == 0)
{
// Set to the default end spawn
spawnPoint = new Location(toWorld, 100, 50, 0);
toWorld.setSpawnLocation(100, 50, 0);
}
if (this.isAllowedOnServer(environment))
{
// To Standard Nether or end
event.setTo(spawnPoint);
}
else
{
// Teleport to standard nether or end
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getPlayer()).
location(spawnPoint).
portal().
build();
}
}
/**
* Handle teleport from or to standard nether or end (end is not possible because EXIT PORTAL triggers RESPAWN event)
* @param event - PlayerPortalEvent
* @param overWorld - over world
* @param environment - environment involved
*/
private void handleFromStandardNetherOrEnd(PlayerPortalEvent event, World overWorld, World.Environment environment)
{
if (environment.equals(World.Environment.NETHER) &&
this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals())
{
// Set to location directly to the from location.
event.setTo(event.getFrom().toVector().toLocation(overWorld));
// Update portal search radius.
this.getIsland(event.getTo()).ifPresent(island ->
event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island)));
event.setCanCreatePortal(true);
// event.setCreationRadius(16); 16 is default creation radius.
}
else
{
// Cannot be portal. Should recalculate position.
// TODO: Currently, it is always spawn location. However, default home must be assigned.
Location toLocation = this.getIsland(overWorld, event.getPlayer()).
map(island -> island.getSpawnPoint(World.Environment.NORMAL)).
orElseGet(() -> {
// If player do not have island, try spawn.
Location spawnLocation = this.getSpawnLocation(overWorld);
return spawnLocation == null ?
event.getFrom().toVector().toLocation(overWorld) :
spawnLocation;
});
event.setTo(toLocation);
}
if (!this.isAllowedOnServer(environment))
{
// Custom portal handling.
event.setCancelled(true);
// Teleport to standard nether or end
ClosestSafeSpotTeleport.builder(this.plugin).
entity(event.getPlayer()).
location(event.getTo()).
portal().
build();
}
}
/**
* Pastes the default nether or end island and teleports the player to the island's spawn point
* @param player - player to teleport after pasting
* @param to - the fallback location if a spawn point is not part of the blueprint
* @param island - the island
* @param environment - NETHER or THE_END
*/
private void pasteNewIsland(Player player,
Location to,
Island island,
World.Environment environment)
{
// Paste then teleport player
this.plugin.getIWM().getAddon(island.getWorld()).ifPresent(addon ->
{
// Get the default bundle's nether or end blueprint
BlueprintBundle blueprintBundle = plugin.getBlueprintsManager().getDefaultBlueprintBundle(addon);
if (blueprintBundle != null)
{
Blueprint bluePrint = this.plugin.getBlueprintsManager().getBlueprints(addon).
get(blueprintBundle.getBlueprint(environment));
if (bluePrint != null)
{
new BlueprintPaster(this.plugin, bluePrint, to.getWorld(), island).
paste().
thenAccept(state -> ClosestSafeSpotTeleport.builder(this.plugin).
entity(player).
location(island.getSpawnPoint(environment) == null ? to : island.getSpawnPoint(environment)).
portal().
build());
}
else
{
this.plugin.logError("Could not paste default island in nether or end. " +
"Is there a nether-island or end-island blueprint?");
}
}
});
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.lists;
import com.google.common.base.Enums;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ -13,57 +14,11 @@ import world.bentobox.bentobox.api.flags.clicklisteners.CycleClick;
import world.bentobox.bentobox.listeners.flags.clicklisteners.CommandRankClickListener;
import world.bentobox.bentobox.listeners.flags.clicklisteners.GeoLimitClickListener;
import world.bentobox.bentobox.listeners.flags.clicklisteners.MobLimitClickListener;
import world.bentobox.bentobox.listeners.flags.protection.BlockInteractionListener;
import world.bentobox.bentobox.listeners.flags.protection.BreakBlocksListener;
import world.bentobox.bentobox.listeners.flags.protection.BreedingListener;
import world.bentobox.bentobox.listeners.flags.protection.BucketListener;
import world.bentobox.bentobox.listeners.flags.protection.DyeListener;
import world.bentobox.bentobox.listeners.flags.protection.EggListener;
import world.bentobox.bentobox.listeners.flags.protection.ElytraListener;
import world.bentobox.bentobox.listeners.flags.protection.EntityInteractListener;
import world.bentobox.bentobox.listeners.flags.protection.ExperiencePickupListener;
import world.bentobox.bentobox.listeners.flags.protection.FireListener;
import world.bentobox.bentobox.listeners.flags.protection.HurtingListener;
import world.bentobox.bentobox.listeners.flags.protection.InventoryListener;
import world.bentobox.bentobox.listeners.flags.protection.ItemDropPickUpListener;
import world.bentobox.bentobox.listeners.flags.protection.LeashListener;
import world.bentobox.bentobox.listeners.flags.protection.LecternListener;
import world.bentobox.bentobox.listeners.flags.protection.LockAndBanListener;
import world.bentobox.bentobox.listeners.flags.protection.PaperExperiencePickupListener;
import world.bentobox.bentobox.listeners.flags.protection.PhysicalInteractionListener;
import world.bentobox.bentobox.listeners.flags.protection.PlaceBlocksListener;
import world.bentobox.bentobox.listeners.flags.protection.PortalListener;
import world.bentobox.bentobox.listeners.flags.protection.ShearingListener;
import world.bentobox.bentobox.listeners.flags.protection.TNTListener;
import world.bentobox.bentobox.listeners.flags.protection.TeleportationListener;
import world.bentobox.bentobox.listeners.flags.protection.ThrowingListener;
import world.bentobox.bentobox.listeners.flags.protection.*;
import world.bentobox.bentobox.listeners.flags.settings.DecayListener;
import world.bentobox.bentobox.listeners.flags.settings.MobSpawnListener;
import world.bentobox.bentobox.listeners.flags.settings.PVPListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.ChestDamageListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.CleanSuperFlatListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.CoarseDirtTillingListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.EnderChestListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.EndermanListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.EnterExitListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.GeoLimitMobsListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.InvincibleVisitorsListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.IslandRespawnListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.ItemFrameListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.LimitMobsListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.LiquidsFlowingOutListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.NaturalSpawningOutsideRangeListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.ObsidianScoopingListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineGrowthListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineRedstoneListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.PistonPushListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.RemoveMobsListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.SpawnerSpawnEggsListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.TreesGrowingOutsideRangeListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.VisitorKeepInventoryListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.WitherListener;
import world.bentobox.bentobox.listeners.flags.worldsettings.*;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
@ -174,7 +129,10 @@ public final class Flags {
public static final Flag BOAT = new Flag.Builder("BOAT", Material.OAK_BOAT).mode(Flag.Mode.BASIC).build();
public static final Flag TRADING = new Flag.Builder("TRADING", Material.EMERALD).defaultSetting(true).mode(Flag.Mode.BASIC).build();
public static final Flag NAME_TAG = new Flag.Builder("NAME_TAG", Material.NAME_TAG).mode(Flag.Mode.ADVANCED).build();
/**
* @since 1.21
*/
public static final Flag ALLAY = new Flag.Builder("ALLAY", Material.AMETHYST_SHARD).mode(Flag.Mode.ADVANCED).build();
// Breeding
public static final Flag BREEDING = new Flag.Builder("BREEDING", Material.CARROT).listener(new BreedingListener()).mode(Flag.Mode.ADVANCED).build();
@ -182,8 +140,16 @@ public final class Flags {
public static final Flag BUCKET = new Flag.Builder("BUCKET", Material.BUCKET).listener(new BucketListener()).mode(Flag.Mode.BASIC).build();
public static final Flag COLLECT_LAVA = new Flag.Builder("COLLECT_LAVA", Material.LAVA_BUCKET).build();
public static final Flag COLLECT_WATER = new Flag.Builder("COLLECT_WATER", Material.WATER_BUCKET).mode(Flag.Mode.ADVANCED).build();
/**
* @since 1.21
*/
public static final Flag COLLECT_POWDERED_SNOW = new Flag.Builder("COLLECT_POWDERED_SNOW", Material.POWDER_SNOW_BUCKET).mode(Flag.Mode.ADVANCED).build();
public static final Flag MILKING = new Flag.Builder("MILKING", Material.MILK_BUCKET).mode(Flag.Mode.ADVANCED).build();
public static final Flag FISH_SCOOPING = new Flag.Builder("FISH_SCOOPING", Material.TROPICAL_FISH_BUCKET).build();
/**
* @since 1.21
*/
public static final Flag AXOLOTL_SCOOPING = new Flag.Builder("AXOLOTL_SCOOPING", Material.AXOLOTL_BUCKET).build();
// Chorus Fruit and Enderpearls
public static final Flag CHORUS_FRUIT = new Flag.Builder("CHORUS_FRUIT", Material.CHORUS_FRUIT).listener(new TeleportationListener()).build();
@ -314,6 +280,32 @@ public final class Flags {
.clickHandler(new CycleClick("CHANGE_SETTINGS", RanksManager.MEMBER_RANK, RanksManager.OWNER_RANK))
.mode(Flag.Mode.TOP_ROW).build();
/**
* This flag allows choosing which island member group can activate sculk sensors.
* TODO: Enums#getIfPresent is used to support 1.18
* @since 1.21.0
*/
public static final Flag SCULK_SENSOR = new Flag.Builder("SCULK_SENSOR", Enums.getIfPresent(Material.class, "SCULK_SENSOR").or(Material.BARRIER)).
listener(new SculkSensorListener()).
type(Type.PROTECTION).
defaultSetting(true).
defaultRank(RanksManager.MEMBER_RANK).
clickHandler(new CycleClick("SCULK_SENSOR", RanksManager.VISITOR_RANK, RanksManager.MEMBER_RANK)).
build();
/**
* This flag allows choosing which island member group can activate sculk shrieker.
* TODO: Enums#getIfPresent is used to support 1.18
* @since 1.21.0
*/
public static final Flag SCULK_SHRIEKER = new Flag.Builder("SCULK_SHRIEKER", Enums.getIfPresent(Material.class, "SCULK_SHRIEKER").or(Material.BARRIER)).
listener(new SculkShriekerListener()).
type(Type.PROTECTION).
defaultSetting(true).
defaultRank(RanksManager.MEMBER_RANK).
clickHandler(new CycleClick("SCULK_SHRIEKER", RanksManager.VISITOR_RANK, RanksManager.MEMBER_RANK)).
build();
/*
* Settings flags (not protection flags)
*/
@ -570,7 +562,21 @@ public final class Flags {
* @see VisitorKeepInventoryListener
*/
public static final Flag VISITOR_KEEP_INVENTORY = new Flag.Builder("VISITOR_KEEP_INVENTORY", Material.TOTEM_OF_UNDYING).listener(new VisitorKeepInventoryListener()).type(Type.WORLD_SETTING).defaultSetting(false).build();
/**
* Toggles whether island visitors can trigger to start a raid on another player's island.
* @since 1.21.0
* @see VisitorsStartingRaidListener
*/
public static final Flag VISITOR_TRIGGER_RAID = new Flag.Builder("VISITOR_TRIGGER_RAID", Material.RAVAGER_SPAWN_EGG).listener(new VisitorsStartingRaidListener()).type(Type.WORLD_SETTING).defaultSetting(true).build();
/**
* Toggles whether entities can teleport between dimensions using portals.
* @since 1.21.0
* @see world.bentobox.bentobox.listeners.teleports.EntityTeleportListener
*/
public static final Flag ENTITY_PORTAL_TELEPORT = new Flag.Builder("ENTITY_PORTAL_TELEPORT", Material.ENDER_EYE).type(Type.WORLD_SETTING).defaultSetting(false).build();
/**
* Provides a list of all the Flag instances contained in this class using reflection.
* Deprecated Flags are ignored.

View File

@ -7,6 +7,7 @@ import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -86,24 +87,24 @@ public class BlueprintClipboardManager {
/**
* Loads a blueprint
* @param fileName - the filename without the suffix
* @param fileName - the sanitized filename without the suffix
* @return the blueprint
* @throws IOException exception if there's an issue loading or unzipping
*/
public Blueprint loadBlueprint(String fileName) throws IOException {
File zipFile = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX);
File zipFile = new File(blueprintFolder, fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (!zipFile.exists()) {
plugin.logError(LOAD_ERROR + zipFile.getName());
throw new IOException(LOAD_ERROR + zipFile.getName());
}
unzip(zipFile.getCanonicalPath());
File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName));
File file = new File(blueprintFolder, fileName);
if (!file.exists()) {
plugin.logError(LOAD_ERROR + file.getName());
throw new IOException(LOAD_ERROR + file.getName() + " temp file");
}
Blueprint bp;
try (FileReader fr = new FileReader(file)) {
try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8)) {
bp = gson.fromJson(fr, Blueprint.class);
} catch (Exception e) {
plugin.logError("Blueprint has JSON error: " + zipFile.getName());
@ -114,7 +115,7 @@ public class BlueprintClipboardManager {
if (bp.getBedrock() == null) {
bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2));
bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString()));
plugin.logWarning("Blueprint " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it.");
plugin.logWarning("Blueprint " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it.");
}
return bp;
}
@ -130,7 +131,7 @@ public class BlueprintClipboardManager {
load(fileName);
} catch (IOException e1) {
user.sendMessage("commands.admin.blueprint.could-not-load");
plugin.logError("Could not load blueprint file: " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage());
plugin.logError("Could not load blueprint file: " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage());
return false;
}
user.sendMessage("general.success");
@ -143,14 +144,20 @@ public class BlueprintClipboardManager {
* @param newName - new name of this blueprint
* @return - true if successful, false if error
*/
public boolean save(User user, String newName) {
if (clipboard.getBlueprint() != null) {
clipboard.getBlueprint().setName(newName);
if (saveBlueprint(clipboard.getBlueprint())) {
public boolean save(User user, String newName, String displayName)
{
if (this.clipboard.isFull())
{
this.clipboard.getBlueprint().setName(newName);
this.clipboard.getBlueprint().setDisplayName(displayName);
if (this.saveBlueprint(this.clipboard.getBlueprint()))
{
user.sendMessage("general.success");
return true;
}
}
user.sendMessage("commands.admin.blueprint.could-not-save", "[message]", "Could not save temp blueprint file.");
return false;
}
@ -165,9 +172,9 @@ public class BlueprintClipboardManager {
plugin.logError("Blueprint name was empty - could not save it");
return false;
}
File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(blueprint.getName()));
File file = new File(blueprintFolder, blueprint.getName());
String toStore = gson.toJson(blueprint, Blueprint.class);
try (FileWriter fileWriter = new FileWriter(file)) {
try (FileWriter fileWriter = new FileWriter(file, StandardCharsets.UTF_8)) {
fileWriter.write(toStore);
} catch (IOException e) {
plugin.logError("Could not save temporary blueprint file: " + file.getName());

View File

@ -1,22 +1,11 @@
package world.bentobox.bentobox.managers;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarFile;
@ -216,13 +205,24 @@ public class BlueprintsManager {
bpf.mkdirs();
}
boolean loaded = false;
File[] bundles = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_BUNDLE_SUFFIX));
File[] bundles = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_BUNDLE_SUFFIX));
if (bundles == null || bundles.length == 0) {
return false;
}
for (File file : bundles) {
try {
BlueprintBundle bb = gson.fromJson(new FileReader(file), BlueprintBundle.class);
try (FileReader fileReader = new FileReader(file, StandardCharsets.UTF_8))
{
if (!file.getName().equals(Util.sanitizeInput(file.getName())))
{
// fail on all blueprints with incorrect names.
throw new InputMismatchException(file.getName());
}
BlueprintBundle bb = gson.fromJson(fileReader, BlueprintBundle.class);
if (bb != null) {
// Make sure there is no existing bundle with the same uniqueId
if (blueprintBundles.get(addon).stream().noneMatch(bundle -> bundle.getUniqueId().equals(bb.getUniqueId()))) {
@ -299,13 +299,17 @@ public class BlueprintsManager {
plugin.logError("There is no blueprint folder for addon " + addon.getDescription().getName());
bpf.mkdirs();
}
File[] bps = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_SUFFIX));
File[] bps = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_SUFFIX));
if (bps == null || bps.length == 0) {
plugin.logError("No blueprints found for " + addon.getDescription().getName());
return;
}
for (File file : bps) {
String fileName = file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length());
// Input sanitization is required for weirdos that edit files manually.
String fileName = Util.sanitizeInput(file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length()));
try {
Blueprint bp = new BlueprintClipboardManager(plugin, bpf).loadBlueprint(fileName);
bp.setName(fileName);
@ -354,9 +358,9 @@ public class BlueprintsManager {
if (!bpf.exists()) {
bpf.mkdirs();
}
File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX);
File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX);
String toStore = gson.toJson(bb, BlueprintBundle.class);
try (FileWriter fileWriter = new FileWriter(fileName)) {
try (FileWriter fileWriter = new FileWriter(fileName, StandardCharsets.UTF_8)) {
fileWriter.write(toStore);
} catch (IOException e) {
plugin.logError("Could not save blueprint bundle file: " + e.getMessage());
@ -364,20 +368,6 @@ public class BlueprintsManager {
});
}
/**
* Sanitizes a filename as much as possible retaining the original name
* @param name - filename to sanitize
* @return sanitized name
*/
public static String sanitizeFileName(String name) {
return name
.chars()
.mapToObj(i -> (char) i)
.map(c -> Character.isWhitespace(c) ? '_' : c)
.filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_')
.map(String::valueOf)
.collect(Collectors.joining());
}
/**
* Saves all the blueprint bundles
@ -405,26 +395,35 @@ public class BlueprintsManager {
* @param name name of the Blueprint to delete
* @since 1.9.0
*/
public void deleteBlueprint(GameModeAddon addon, String name) {
List<Blueprint> addonBlueprints = blueprints.get(addon);
public void deleteBlueprint(GameModeAddon addon, String name)
{
List<Blueprint> addonBlueprints = this.blueprints.get(addon);
Iterator<Blueprint> it = addonBlueprints.iterator();
while (it.hasNext()) {
Blueprint b = it.next();
if (b.getName().equalsIgnoreCase(name)) {
it.remove();
blueprints.put(addon, addonBlueprints);
File file = new File(getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX);
while (it.hasNext())
{
Blueprint b = it.next();
if (b.getName().equalsIgnoreCase(name))
{
it.remove();
File file = new File(this.getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX);
// Delete the file
try {
try
{
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage());
}
catch (IOException e)
{
this.plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage());
}
}
}
}
/**
* Paste the islands to world
*
@ -450,7 +449,7 @@ public class BlueprintsManager {
plugin.logError("Tried to paste '" + name + "' but the bundle is not loaded!");
return false;
}
BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase(Locale.ENGLISH));
BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase());
if (!blueprints.containsKey(addon) || blueprints.get(addon).isEmpty()) {
plugin.logError("No blueprints loaded for bundle '" + name + "'!");
return false;
@ -467,7 +466,7 @@ public class BlueprintsManager {
// Paste
if (bp != null) {
new BlueprintPaster(plugin, bp, addon.getOverWorld(), island).paste().thenAccept(b -> pasteNether(addon, bb, island).thenAccept(b2 ->
pasteEnd(addon, bb, island).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task))));
pasteEnd(addon, bb, island).thenAccept(message -> sendMessage(island)).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task))));
}
return true;
@ -500,6 +499,18 @@ public class BlueprintsManager {
return CompletableFuture.completedFuture(false);
}
/**
* This method just sends a message to the island owner that island creating is completed.
* @param island Island which owner must receive a message.
*/
private void sendMessage(Island island) {
if (island != null && island.getOwner() != null) {
final Optional<User> owner = Optional.of(island).map(i -> User.getInstance(i.getOwner()));
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done"));
}
}
/**
* Validate if the bundle name is valid or not
*
@ -511,7 +522,7 @@ public class BlueprintsManager {
if (name == null) {
return null;
}
if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name.toLowerCase(Locale.ENGLISH))) {
if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name)) {
return name;
}
return null;
@ -543,7 +554,7 @@ public class BlueprintsManager {
// Permission
String permission = addon.getPermissionPrefix() + "island.create." + name;
// Get Blueprint bundle
BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase(Locale.ENGLISH));
BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase());
if (bb == null || (bb.isRequirePermission() && !name.equals(DEFAULT_BUNDLE_NAME) && !user.hasPermission(permission))) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission);
return false;
@ -562,7 +573,7 @@ public class BlueprintsManager {
blueprintBundles.get(addon).removeIf(k -> k.getUniqueId().equals(bb.getUniqueId()));
}
File bpf = getBlueprintsFolder(addon);
File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX);
File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX);
try {
Files.deleteIfExists(fileName.toPath());
} catch (IOException e) {
@ -576,25 +587,39 @@ public class BlueprintsManager {
* @param addon - Game Mode Addon
* @param bp - blueprint
* @param name - new name
* @param displayName - display name for blueprint
*/
public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name) {
if (bp.getName().equalsIgnoreCase(name)) {
public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name, String displayName)
{
if (bp.getName().equalsIgnoreCase(name))
{
// If the name is the same, do not do anything
return;
}
File bpf = getBlueprintsFolder(addon);
// Get the filename
File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX);
// Delete the old file
try {
Files.deleteIfExists(fileName.toPath());
} catch (IOException e) {
plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage());
}
// Set new name
bp.setName(name.toLowerCase(Locale.ENGLISH));
// Save it
saveBlueprint(addon, bp);
}
File bpf = this.getBlueprintsFolder(addon);
// Get the filename
File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX);
// Delete the old file
try
{
Files.deleteIfExists(fileName.toPath());
}
catch (IOException e)
{
this.plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage());
}
// Remove blueprint from the blueprints.
this.blueprints.get(addon).remove(bp);
// Set new name
bp.setName(name);
bp.setDisplayName(displayName);
// Save it
this.saveBlueprint(addon, bp);
this.addBlueprint(addon, bp);
}
}

View File

@ -171,10 +171,13 @@ public class IslandWorldManager {
}
// Set default island settings
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION))
.forEach(f -> settings.getDefaultIslandFlags().putIfAbsent(f, f.getDefaultRank()));
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING))
.forEach(f -> settings.getDefaultIslandSettings().putIfAbsent(f, f.getDefaultRank()));
plugin.getFlagsManager().getFlags().stream().
filter(f -> f.getType().equals(Flag.Type.PROTECTION)).
forEach(f -> settings.getDefaultIslandFlagNames().putIfAbsent(f.getID(), f.getDefaultRank()));
plugin.getFlagsManager().getFlags().stream().
filter(f -> f.getType().equals(Flag.Type.SETTING)).
forEach(f -> settings.getDefaultIslandSettingNames().putIfAbsent(f.getID(), f.getDefaultRank()));
Bukkit.getScheduler().runTask(plugin, () -> {
// Set world difficulty
Difficulty diff = settings.getDifficulty();
@ -229,13 +232,13 @@ public class IslandWorldManager {
}
/**
* Value will always be greater than 0 and less than the world's max height.
* Value will always be greater than the world's min height and less than the world's max height.
* @return the islandHeight
*/
public int getIslandHeight(@NonNull World world) {
if (gameModes.containsKey(world) && world.getMaxHeight() > 0) {
return Math.min(world.getMaxHeight() - 1,
Math.max(0, gameModes.get(world).getWorldSettings().getIslandHeight()));
Math.max(world.getMinHeight(), gameModes.get(world).getWorldSettings().getIslandHeight()));
}
return 0;
}
@ -477,7 +480,9 @@ public class IslandWorldManager {
* @return Friendly name or world name if world is not a game world
*/
public String getFriendlyName(@NonNull World world) {
return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getFriendlyName() : world.getName();
return gameModes.containsKey(world) ?
gameModes.get(world).getWorldSettings().getFriendlyName() :
world.getName();
}
/**
@ -699,8 +704,11 @@ public class IslandWorldManager {
* @param world - world
* @return default rank settings for new islands.
*/
public Map<Flag, Integer> getDefaultIslandFlags(@NonNull World world) {
return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getDefaultIslandFlags() : Collections.emptyMap();
public Map<Flag, Integer> getDefaultIslandFlags(@NonNull World world)
{
return this.gameModes.containsKey(world) ?
this.convertToFlags(this.gameModes.get(world).getWorldSettings().getDefaultIslandFlagNames()) :
Collections.emptyMap();
}
/**
@ -715,12 +723,14 @@ public class IslandWorldManager {
/**
* Return island setting defaults for world
*
* @param world
* - world
* @param world - world
* @return default settings for new islands
*/
public Map<Flag, Integer> getDefaultIslandSettings(@NonNull World world) {
return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getDefaultIslandSettings() : Collections.emptyMap();
public Map<Flag, Integer> getDefaultIslandSettings(@NonNull World world)
{
return this.gameModes.containsKey(world) ?
this.convertToFlags(this.gameModes.get(world).getWorldSettings().getDefaultIslandSettingNames()) :
Collections.emptyMap();
}
public boolean isUseOwnGenerator(@NonNull World world) {
@ -921,4 +931,18 @@ public class IslandWorldManager {
return gameModes.containsKey(world) && gameModes.get(world).getWorldSettings().isTeleportPlayerToIslandUponIslandCreation();
}
/**
* This method migrates Map of String, Integer to Map of Flag, Integer.
* @param flagNamesMap Map that contains flag names to their values.
* @return Flag objects to their values.
* @since 1.21
*/
private Map<Flag, Integer> convertToFlags(Map<String, Integer> flagNamesMap)
{
Map<Flag, Integer> flagMap = new HashMap<>();
flagNamesMap.forEach((key, value) ->
this.plugin.getFlagsManager().getFlag(key).ifPresent(flag -> flagMap.put(flag, value)));
return flagMap;
}
}

View File

@ -1333,7 +1333,7 @@ public class IslandsManager {
} else {
// Successful load
// Clean any null flags out of the island - these can occur for various reasons
island.getFlags().keySet().removeIf(f -> f.getID().startsWith("NULL_FLAG"));
island.getFlags().keySet().removeIf(f -> f.startsWith("NULL_FLAG"));
}
}

View File

@ -1,49 +0,0 @@
package world.bentobox.bentobox.nms;
import org.bukkit.Chunk;
import org.bukkit.block.data.BlockData;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.util.BoundingBox;
public interface NMSAbstraction {
/**
* Copy the chunk data and biome grid to the given chunk.
* @param chunk - chunk to copy to
* @param chunkData - chunk data to copy
* @param biomeGrid - biome grid to copy to
* @param limitBox - bounding box to limit the copying
*/
default void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) {
double baseX = chunk.getX() << 4;
double baseZ = chunk.getZ() << 4;
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (!limitBox.contains(baseX + x, 0, baseZ + z)) {
continue;
}
for (int y = minHeight; y < maxHeight; y++) {
setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false);
// 3D biomes, 4 blocks separated
if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) {
chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z));
}
}
}
}
}
/**
* Update the low-level chunk information for the given block to the new block ID and data. This
* change will not be propagated to clients until the chunk is refreshed to them.
* @param chunk - chunk to be changed
* @param x - x coordinate within chunk 0 - 15
* @param y - y coordinate within chunk 0 - world height, e.g. 255
* @param z - z coordinate within chunk 0 - 15
* @param blockData - block data to set the block
* @param applyPhysics - apply physics or not
*/
void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics);
}

View File

@ -0,0 +1,36 @@
package world.bentobox.bentobox.nms;
import org.bukkit.Location;
import org.bukkit.World;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* A helper class for {@link world.bentobox.bentobox.blueprints.BlueprintPaster}
*/
public interface PasteHandler {
/**
* Create a future to paste the blocks
*
* @param island the island
* @param world the world
* @param blockMap the block map
* @return the future
*/
CompletableFuture<Void> pasteBlocks(Island island, World world, Map<Location, BlueprintBlock> blockMap);
/**
* Create a future to paste the entities
*
* @param island the island
* @param world the world
* @param entityMap the entities map
* @return the future
*/
CompletableFuture<Void> pasteEntities(Island island, World world, Map<Location, List<BlueprintEntity>> entityMap);
}

View File

@ -0,0 +1,137 @@
package world.bentobox.bentobox.nms;
import io.papermc.lib.PaperLib;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.BoundingBox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.util.MyBiomeGrid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
public abstract class SimpleWorldRegenerator implements WorldRegenerator {
private final BentoBox plugin;
protected SimpleWorldRegenerator() {
this.plugin = BentoBox.getInstance();
}
/**
* Update the low-level chunk information for the given block to the new block ID and data. This
* change will not be propagated to clients until the chunk is refreshed to them.
*
* @param chunk - chunk to be changed
* @param x - x coordinate within chunk 0 - 15
* @param y - y coordinate within chunk 0 - world height, e.g. 255
* @param z - z coordinate within chunk 0 - 15
* @param blockData - block data to set the block
* @param applyPhysics - apply physics or not
*/
protected abstract void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics);
@Override
public CompletableFuture<Void> regenerate(GameModeAddon gm, IslandDeletion di, World world) {
CompletableFuture<Void> bigFuture = new CompletableFuture<>();
new BukkitRunnable() {
private int chunkX = di.getMinXChunk();
private int chunkZ = di.getMinZChunk();
CompletableFuture<Void> currentTask = CompletableFuture.completedFuture(null);
@Override
public void run() {
if (!currentTask.isDone()) return;
if (isEnded(chunkX)) {
cancel();
bigFuture.complete(null);
return;
}
List<CompletableFuture<Void>> newTasks = new ArrayList<>();
for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) {
if (isEnded(chunkX)) {
break;
}
final int x = chunkX;
final int z = chunkZ;
newTasks.add(regenerateChunk(gm, di, world, x, z));
chunkZ++;
if (chunkZ > di.getMaxZChunk()) {
chunkZ = di.getMinZChunk();
chunkX++;
}
}
currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0]));
}
private boolean isEnded(int chunkX) {
return chunkX > di.getMaxXChunk();
}
}.runTaskTimer(plugin, 0L, 20L);
return bigFuture;
}
@SuppressWarnings("deprecation")
private CompletableFuture<Void> regenerateChunk(GameModeAddon gm, IslandDeletion di, World world, int chunkX, int chunkZ) {
CompletableFuture<Chunk> chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ);
CompletableFuture<Void> invFuture = chunkFuture.thenAccept(chunk ->
Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance)
.filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ()))
.forEach(te -> ((InventoryHolder) te).getInventory().clear())
);
CompletableFuture<Void> entitiesFuture = chunkFuture.thenAccept(chunk -> {
for (Entity e : chunk.getEntities()) {
if (!(e instanceof Player)) {
e.remove();
}
}
});
CompletableFuture<Chunk> copyFuture = chunkFuture.thenApply(chunk -> {
// Reset blocks
MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment());
ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete");
// Will be null if use-own-generator is set to true
if (cg != null) {
ChunkGenerator.ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid);
copyChunkDataToChunk(chunk, cd, grid, di.getBox());
}
return chunk;
});
CompletableFuture<Void> postCopyFuture = copyFuture.thenAccept(chunk -> {
// Remove all entities in chunk, including any dropped items as a result of clearing the blocks above
Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove);
});
return CompletableFuture.allOf(invFuture, entitiesFuture, postCopyFuture);
}
private void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) {
double baseX = chunk.getX() << 4;
double baseZ = chunk.getZ() << 4;
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (!limitBox.contains(baseX + x, 0, baseZ + z)) {
continue;
}
for (int y = minHeight; y < maxHeight; y++) {
setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false);
// 3D biomes, 4 blocks separated
if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) {
chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z));
}
}
}
}
}
}

View File

@ -0,0 +1,22 @@
package world.bentobox.bentobox.nms;
import org.bukkit.World;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import java.util.concurrent.CompletableFuture;
/**
* A world generator used by {@link world.bentobox.bentobox.util.DeleteIslandChunks}
*/
public interface WorldRegenerator {
/**
* Create a future to regenerate the regions of the island.
*
* @param gm the game mode
* @param di the island deletion
* @param world the world
* @return the completable future
*/
CompletableFuture<Void> regenerate(GameModeAddon gm, IslandDeletion di, World world);
}

View File

@ -1,20 +0,0 @@
package world.bentobox.bentobox.nms.fallback;
import org.bukkit.Chunk;
import org.bukkit.block.data.BlockData;
import world.bentobox.bentobox.nms.NMSAbstraction;
/**
* @author tastybento
*
*/
public class NMSHandler implements NMSAbstraction {
@Override
public void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) {
chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics);
}
}

View File

@ -0,0 +1,40 @@
package world.bentobox.bentobox.nms.fallback;
import org.bukkit.Location;
import org.bukkit.World;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.nms.PasteHandler;
import world.bentobox.bentobox.util.DefaultPasteUtil;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class PasteHandlerImpl implements PasteHandler {
@Override
public CompletableFuture<Void> pasteBlocks(Island island, World world, Map<Location, BlueprintBlock> blockMap) {
return blockMap.entrySet().parallelStream()
.map(entry -> DefaultPasteUtil.setBlock(island, entry.getKey(), entry.getValue()))
.collect(
Collectors.collectingAndThen(
Collectors.toList(),
list -> CompletableFuture.allOf(list.toArray(new CompletableFuture[0]))
)
);
}
@Override
public CompletableFuture<Void> pasteEntities(Island island, World world, Map<Location, List<BlueprintEntity>> entityMap) {
return entityMap.entrySet().parallelStream()
.map(entry -> DefaultPasteUtil.setEntity(island, entry.getKey(), entry.getValue()))
.collect(
Collectors.collectingAndThen(
Collectors.toList(),
list -> CompletableFuture.allOf(list.toArray(new CompletableFuture[0]))
)
);
}
}

View File

@ -0,0 +1,20 @@
package world.bentobox.bentobox.nms.fallback;
import org.bukkit.Chunk;
import org.bukkit.block.data.BlockData;
import world.bentobox.bentobox.nms.SimpleWorldRegenerator;
/**
* @author tastybento
*
*/
public class WorldRegeneratorImpl extends SimpleWorldRegenerator {
@Override
protected void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) {
chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics);
}
}

View File

@ -1,19 +1,19 @@
package world.bentobox.bentobox.nms.v1_18_R1;
package world.bentobox.bentobox.nms.v1_19_R1;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.World;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.chunk.Chunk;
import world.bentobox.bentobox.nms.NMSAbstraction;
import world.bentobox.bentobox.nms.SimpleWorldRegenerator;
public class NMSHandler implements NMSAbstraction {
public class WorldRegeneratorImpl extends SimpleWorldRegenerator {
private static final IBlockData AIR = ((CraftBlockData) Bukkit.createBlockData(Material.AIR)).getState();

View File

@ -0,0 +1,238 @@
package world.bentobox.bentobox.util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.*;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.nms.PasteHandler;
import java.util.*;
import java.util.concurrent.CompletableFuture;
/**
* A utility class for {@link PasteHandler}
*
* @author tastybento
*/
public class DefaultPasteUtil {
private static final String MINECRAFT = "minecraft:";
private static final Map<String, String> BLOCK_CONVERSION = Map.of("sign", "oak_sign", "wall_sign", "oak_wall_sign");
private static final BentoBox plugin;
static {
plugin = BentoBox.getInstance();
}
/**
* Set the block to the location
*
* @param island - island
* @param location - location
* @param bpBlock - blueprint block
*/
public static CompletableFuture<Void> 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 = createBlockData(bpBlock);
block.setBlockData(bd, false);
setBlockState(island, block, bpBlock);
// Set biome
if (bpBlock.getBiome() != null) {
block.setBiome(bpBlock.getBiome());
}
});
}
/**
* Create a block data from the blueprint
*
* @param block - blueprint block
* @return the block data
*/
public static BlockData createBlockData(BlueprintBlock block) {
try {
return Bukkit.createBlockData(block.getBlockData());
} catch (Exception e) {
return convertBlockData(block);
}
}
/**
* Convert the blueprint to block data
*
* @param block - the blueprint block
* @return the block data
*/
public static BlockData convertBlockData(BlueprintBlock block) {
BlockData blockData = Bukkit.createBlockData(Material.AIR);
try {
for (Map.Entry<String, String> en : BLOCK_CONVERSION.entrySet()) {
if (block.getBlockData().startsWith(MINECRAFT + en.getKey())) {
blockData = Bukkit.createBlockData(block.getBlockData().replace(MINECRAFT + en.getKey(), MINECRAFT + en.getValue()));
break;
}
}
} catch (IllegalArgumentException e) {
// This may happen if the block type is no longer supported by the server
plugin.logWarning("Blueprint references materials not supported on this server version.");
plugin.logWarning("Load blueprint manually, check and save to fix for this server version.");
plugin.logWarning("Failed block data: " + block.getBlockData());
}
return blockData;
}
/**
* Handles signs, chests and mob spawner blocks
*
* @param island - island
* @param block - block
* @param bpBlock - config
*/
public static void setBlockState(Island island, Block block, BlueprintBlock bpBlock) {
// Get the block state
BlockState bs = block.getState();
// Signs
if (bs instanceof Sign) {
writeSign(island, block, bpBlock.getSignLines(), bpBlock.isGlowingText());
}
// Chests, in general
else if (bs instanceof InventoryHolder holder) {
Inventory ih = holder.getInventory();
// Double chests are pasted as two blocks so inventory is filled twice.
// This code stops over-filling for the first block.
bpBlock.getInventory().forEach(ih::setItem);
}
// Mob spawners
else if (bs instanceof CreatureSpawner spawner) {
setSpawner(spawner, bpBlock.getCreatureSpawner());
}
// Banners
else if (bs instanceof Banner banner && bpBlock.getBannerPatterns() != null) {
bpBlock.getBannerPatterns().removeIf(Objects::isNull);
banner.setPatterns(bpBlock.getBannerPatterns());
banner.update(true, false);
}
}
/**
* Set the spawner setting from the blueprint
*
* @param spawner - spawner
* @param s - blueprint spawner
*/
public static void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner s) {
spawner.setSpawnedType(s.getSpawnedType());
spawner.setMaxNearbyEntities(s.getMaxNearbyEntities());
spawner.setMaxSpawnDelay(s.getMaxSpawnDelay());
spawner.setMinSpawnDelay(s.getMinSpawnDelay());
spawner.setDelay(s.getDelay());
spawner.setRequiredPlayerRange(s.getRequiredPlayerRange());
spawner.setSpawnRange(s.getSpawnRange());
spawner.update(true, false);
}
/**
* Spawn the blueprint entities to the location
*
* @param island - island
* @param location - location
* @param list - blueprint entities
*/
public static CompletableFuture<Void> setEntity(Island island, Location location, List<BlueprintEntity> list) {
World world = location.getWorld();
assert world != null;
return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> {
LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType());
if (k.getCustomName() != null) {
String customName = k.getCustomName();
if (island != null) {
// Parse any placeholders in the entity's name, if the owner's connected (he should)
Optional<Player> owner = Optional.ofNullable(island.getOwner())
.map(User::getInstance)
.map(User::getPlayer);
if (owner.isPresent()) {
// Parse for the player's name first (in case placeholders might need it)
customName = customName.replace(TextVariables.NAME, owner.get().getName());
// Now parse the placeholders
customName = plugin.getPlaceholdersManager().replacePlaceholders(owner.get(), customName);
}
}
// Actually set the custom name
e.setCustomName(customName);
}
k.configureEntity(e);
}));
}
/**
* Write the lines to the sign at the block
*
* @param island - island
* @param block - block
* @param lines - lines
* @param glow - is sign glowing?
*/
public static void writeSign(Island island, final Block block, final List<String> lines, boolean glow) {
BlockFace bf;
if (block.getType().name().contains("WALL_SIGN")) {
WallSign wallSign = (WallSign) block.getBlockData();
bf = wallSign.getFacing();
} else {
org.bukkit.block.data.type.Sign sign = (org.bukkit.block.data.type.Sign) block.getBlockData();
bf = sign.getRotation();
}
// Handle spawn sign
if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.SPAWN_HERE)) {
block.setType(Material.AIR);
// Orient to face same direction as sign
Location spawnPoint = new Location(block.getWorld(), block.getX() + 0.5D, block.getY(),
block.getZ() + 0.5D, Util.blockFaceToFloat(bf.getOppositeFace()), 30F);
island.setSpawnPoint(block.getWorld().getEnvironment(), spawnPoint);
return;
}
// Get the name of the player
String name = "";
if (island != null) {
name = plugin.getPlayers().getName(island.getOwner());
}
// Handle locale text for starting sign
org.bukkit.block.Sign s = (org.bukkit.block.Sign) block.getState();
// Sign text must be stored under the addon's name.sign.line0,1,2,3 in the yaml file
if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.START_TEXT)) {
// Get the addon that is operating in this world
String addonName = plugin.getIWM().getAddon(island.getWorld()).map(addon -> addon.getDescription().getName().toLowerCase(Locale.ENGLISH)).orElse("");
Optional<User> user = Optional.ofNullable(island.getOwner()).map(User::getInstance);
if (user.isPresent()) {
for (int i = 0; i < 4; i++) {
s.setLine(i, Util.translateColorCodes(plugin.getLocalesManager().getOrDefault(user.get(),
addonName + ".sign.line" + i, "").replace(TextVariables.NAME, name)));
}
}
} else {
// Just paste
for (int i = 0; i < 4; i++) {
s.setLine(i, lines.get(i));
}
}
s.setGlowingText(glow);
// Update the sign
s.update();
}
}

View File

@ -1,29 +1,16 @@
package world.bentobox.bentobox.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.ChunkGenerator.ChunkData;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.scheduler.BukkitTask;
import io.papermc.lib.PaperLib;
import org.bukkit.scheduler.BukkitRunnable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.nms.NMSAbstraction;
import world.bentobox.bentobox.nms.WorldRegenerator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Deletes islands chunk by chunk
@ -37,16 +24,10 @@ public class DeleteIslandChunks {
private final World netherWorld;
private final World endWorld;
private final AtomicBoolean completed;
private final NMSAbstraction nms;
private int chunkX;
private int chunkZ;
private BukkitTask task;
private CompletableFuture<Void> currentTask = CompletableFuture.completedFuture(null);
private final WorldRegenerator regenerator;
public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) {
this.plugin = plugin;
this.chunkX = di.getMinXChunk();
this.chunkZ = di.getMinZChunk();
this.di = di;
completed = new AtomicBoolean(false);
// Nether
@ -61,9 +42,9 @@ public class DeleteIslandChunks {
} else {
endWorld = null;
}
// NMS
this.nms = Util.getNMS();
if (nms == null) {
// Regenerator
this.regenerator = Util.getRegenerator();
if (regenerator == null) {
plugin.logError("Could not delete chunks because of NMS error");
return;
}
@ -75,37 +56,23 @@ public class DeleteIslandChunks {
}
private void regenerateChunks() {
// Run through all chunks of the islands and regenerate them.
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (!currentTask.isDone()) return;
if (isEnded(chunkX)) {
finish();
return;
}
List<CompletableFuture<Void>> newTasks = new ArrayList<>();
for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) {
if (isEnded(chunkX)) {
break;
}
final int x = chunkX;
final int z = chunkZ;
plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> {
newTasks.add(processChunk(gm, di.getWorld(), x, z)); // Overworld
newTasks.add(processChunk(gm, netherWorld, x, z)); // Nether
newTasks.add(processChunk(gm, endWorld, x, z)); // End
});
chunkZ++;
if (chunkZ > di.getMaxZChunk()) {
chunkZ = di.getMinZChunk();
chunkX++;
CompletableFuture<Void> all = plugin.getIWM().getAddon(di.getWorld())
.map(gm -> new CompletableFuture[]{
processWorld(gm, di.getWorld()), // Overworld
processWorld(gm, netherWorld), // Nether
processWorld(gm, endWorld) // End
})
.map(CompletableFuture::allOf)
.orElseGet(() -> CompletableFuture.completedFuture(null));
new BukkitRunnable() {
@Override
public void run() {
if (all.isDone()) {
finish();
cancel();
}
}
currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0]));
}, 0L, 20L);
}
private boolean isEnded(int chunkX) {
return chunkX > di.getMaxXChunk();
}.runTaskTimer(plugin, 0, 20);
}
private void finish() {
@ -113,44 +80,16 @@ public class DeleteIslandChunks {
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
// We're done
completed.set(true);
task.cancel();
}
private CompletableFuture<Void> processChunk(GameModeAddon gm, World world, int x, int z) {
private CompletableFuture<Void> processWorld(GameModeAddon gm, World world) {
if (world != null) {
return PaperLib.getChunkAtAsync(world, x, z).thenAccept(chunk -> regenerateChunk(gm, chunk, x, z));
return regenerator.regenerate(gm, di, world);
} else {
return CompletableFuture.completedFuture(null);
}
}
private void regenerateChunk(GameModeAddon gm, Chunk chunk, int x, int z) {
// Clear all inventories
Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance)
.filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ()))
.forEach(te -> ((InventoryHolder) te).getInventory().clear());
// Remove all entities
for (Entity e : chunk.getEntities()) {
if (!(e instanceof Player)) {
e.remove();
}
}
// Reset blocks
MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment());
ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete");
// Will be null if use-own-generator is set to true
if (cg != null) {
ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid);
createChunk(cd, chunk, grid);
}
}
private void createChunk(ChunkData cd, Chunk chunk, MyBiomeGrid grid) {
nms.copyChunkDataToChunk(chunk, cd, grid, di.getBox());
// Remove all entities in chunk, including any dropped items as a result of clearing the blocks above
Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove);
}
public boolean isCompleted() {
return completed.get();
}

View File

@ -1,7 +1,9 @@
package world.bentobox.bentobox.util;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.MissingFormatArgumentException;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -56,38 +58,78 @@ public class ItemParser {
return defaultItemStack;
}
ItemStack returnValue = defaultItemStack;
String[] part = text.split(":");
try {
// Because I am lazy, and do not want to rewrite every parser, I will just add custom data as
// parameter and remove that array part form input data.
Optional<String> first = Arrays.stream(part).filter(field -> field.matches("(CMD-[0-9]*)")).findFirst();
Integer customModelData = null;
if (first.isPresent()) {
// Ugly and fast way how to get rid of customData field.
String[] copyParts = new String[part.length - 1];
int j = 0;
for (String field : part) {
if (!field.matches("(CMD-[0-9]*)")) {
copyParts[j++] = field;
}
}
// Replace original parts with the copy parts that does not have any CMD values.
part = copyParts;
// Now use value from Custom Data Model and parse it as integer.
customModelData = Integer.valueOf(first.get().replaceFirst("CMD-", ""));
}
// Check if there are more properties for the item stack
if (part.length == 1) {
// Parse material directly. It does not have any extra properties.
return new ItemStack(Material.valueOf(text.toUpperCase()));
returnValue = new ItemStack(Material.valueOf(part[0].toUpperCase()));
}
// Material-specific handling
else if (part[0].contains("POTION") || part[0].equalsIgnoreCase("TIPPED_ARROW")) {
// Parse Potions and Tipped Arrows
return parsePotion(part);
returnValue = parsePotion(part);
} else if (part[0].contains("BANNER")) {
// Parse Banners
return parseBanner(part);
returnValue = parseBanner(part);
} else if (part[0].equalsIgnoreCase("PLAYER_HEAD")) {
// Parse Player Heads
return parsePlayerHead(part);
returnValue = parsePlayerHead(part);
}
// Generic handling
else if (part.length == 2) {
// Material:Qty
return parseItemQuantity(part);
returnValue = parseItemQuantity(part);
} else if (part.length == 3) {
// Material:Durability:Qty
return parseItemDurabilityAndQuantity(part);
returnValue = parseItemDurabilityAndQuantity(part);
}
if (returnValue != null) {
// If wrapper is just for code-style null-pointer checks.
if (customModelData != null) {
// We have custom data model. Now assign it to the item-stack.
ItemMeta itemMeta = returnValue.getItemMeta();
// Another null-pointer check for materials that does not have item meta.
if (itemMeta != null) {
itemMeta.setCustomModelData(customModelData);
// Update meta to the return item.
returnValue.setItemMeta(itemMeta);
}
}
}
} catch (Exception exception) {
BentoBox.getInstance().logError("Could not parse item " + text + " " + exception.getLocalizedMessage());
returnValue = defaultItemStack;
}
return defaultItemStack;
return returnValue;
}

View File

@ -2,11 +2,7 @@ package world.bentobox.bentobox.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@ -25,19 +21,7 @@ import org.bukkit.World.Environment;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
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;
@ -47,7 +31,10 @@ import io.papermc.lib.PaperLib;
import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.nms.NMSAbstraction;
import world.bentobox.bentobox.nms.PasteHandler;
import world.bentobox.bentobox.nms.WorldRegenerator;
import world.bentobox.bentobox.versions.ServerCompatibility;
/**
* A set of utility methods
@ -64,7 +51,8 @@ public class Util {
private static final String THE_END = "_the_end";
private static String serverVersion = null;
private static BentoBox plugin = BentoBox.getInstance();
private static NMSAbstraction nms = null;
private static PasteHandler pasteHandler = null;
private static WorldRegenerator regenerator = null;
private Util() {}
@ -353,8 +341,19 @@ public class Util {
// Bat extends Mob
// Most of passive mobs extends Animals
return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman ||
if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18,
ServerCompatibility.ServerVersion.V1_18_1,
ServerCompatibility.ServerVersion.V1_18_2))
{
return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman ||
entity instanceof WaterMob && !(entity instanceof PufferFish) || entity instanceof Bat;
}
else
{
return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman ||
entity instanceof WaterMob && !(entity instanceof PufferFish) || entity instanceof Bat ||
entity instanceof Allay;
}
}
/*
@ -689,23 +688,56 @@ public class Util {
}
/**
* Set the NMS handler the plugin will use
* @param nms the NMS handler
* Set the regenerator the plugin will use
* @param regenerator the regenerator
*/
public static void setNms(NMSAbstraction nms) {
Util.nms = nms;
public static void setRegenerator(WorldRegenerator regenerator) {
Util.regenerator = regenerator;
}
/**
* Get the NMS handler the plugin will use
* Get the regenerator the plugin will use
* @return an accelerated regenerator class for this server
*/
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");
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.");
handler = new world.bentobox.bentobox.nms.fallback.WorldRegeneratorImpl();
}
setRegenerator(handler);
}
return regenerator;
}
/**
* Set the paste handler the plugin will use
* @param pasteHandler the NMS paster
*/
public static void setPasteHandler(PasteHandler pasteHandler) {
Util.pasteHandler = pasteHandler;
}
/**
* Get the paste handler the plugin will use
* @return an NMS accelerated class for this server
*/
public static NMSAbstraction getNMS() {
if (nms == null) {
plugin.log("No NMS Handler was set, falling back to Bukkit API.");
setNms(new world.bentobox.bentobox.nms.fallback.NMSHandler());
public static PasteHandler getPasteHandler() {
if (pasteHandler == null) {
setPasteHandler(new world.bentobox.bentobox.nms.fallback.PasteHandlerImpl());
}
return nms;
return pasteHandler;
}
/**
@ -725,4 +757,19 @@ public class Util {
}
return count;
}
/**
* This method removes all special characters that are not allowed in filenames (windows).
* It also includes any white-spaces, as for some reason, I do like it more without them.
* Also, all cases are lower cased for easier blueprint mapping.
* @param input Input that need to be sanitized.
* @return A sanitized input without illegal characters in names.
*/
public static String sanitizeInput(String input)
{
return ChatColor.stripColor(
Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))).
toLowerCase();
}
}

View File

@ -0,0 +1,887 @@
//
// Created by BONNe
// Copyright - 2022
//
package world.bentobox.bentobox.util.teleport;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
public class ClosestSafeSpotTeleport
{
/**
* Teleports and entity to a safe spot on island
*
* @param builder - safe spot teleport builder
*/
ClosestSafeSpotTeleport(Builder builder)
{
this.plugin = builder.getPlugin();
this.entity = builder.getEntity();
this.location = builder.getLocation();
this.portal = builder.isPortal();
this.successRunnable = builder.getSuccessRunnable();
this.failRunnable = builder.getFailRunnable();
this.failureMessage = builder.getFailureMessage();
this.result = builder.getResult();
this.world = Objects.requireNonNull(this.location.getWorld());
this.cancelIfFail = builder.isCancelIfFail();
// Try starting location
Util.getChunkAtAsync(this.location).thenRun(this::checkLocation);
}
/**
* This is main method that triggers safe spot search.
* It starts with the given location and afterwards checks all blocks in required area.
*/
private void checkLocation()
{
if (this.plugin.getIslandsManager().isSafeLocation(this.location))
{
if (!this.portal)
{
// If this is not a portal teleport, then go to the safe location immediately
this.teleportEntity(this.location);
// Position search is completed. Quit faster.
return;
}
}
// Players should not be teleported outside protection range if they already are in it.
this.boundingBox = this.plugin.getIslandsManager().getIslandAt(this.location).
map(Island::getProtectionBoundingBox).
orElseGet(() -> {
int protectionRange = this.plugin.getIWM().getIslandProtectionRange(this.world);
return new BoundingBox(this.location.getBlockX() - protectionRange,
Math.max(this.world.getMinHeight(), this.location.getBlockY() - protectionRange),
this.location.getBlockZ() - protectionRange,
this.location.getBlockX() + protectionRange,
Math.min(this.world.getMaxHeight(), this.location.getBlockY() + protectionRange),
this.location.getBlockZ() + protectionRange);
});
// The maximal range of search.
this.range = Math.min(this.plugin.getSettings().getSafeSpotSearchRange(), (int) this.boundingBox.getWidthX() / 2);
// The block queue contains all possible positions where player can be teleported. The queue will not be populated
// with all blocks, as the validation would not allow it.ss
this.blockQueue = new PriorityQueue<>(this.range * 2, ClosestSafeSpotTeleport.POSITION_COMPARATOR);
// Get chunks to scan
this.chunksToScanIterator = this.getChunksToScan().iterator();
// Start a recurring task until done or cancelled
this.task = Bukkit.getScheduler().runTaskTimer(this.plugin, this::gatherChunks, 0L, CHUNK_LOAD_SPEED);
}
/**
* This method loads all chunks in async and populates blockQueue with all blocks.
*/
private void gatherChunks()
{
// Set a flag so this is only run if it's not already in progress
if (this.checking.get())
{
return;
}
this.checking.set(true);
if (!this.portal && !this.blockQueue.isEmpty() && this.blockQueue.peek().distance() < 5)
{
// Position is found? Well most likely (not in all situations) position in block queue is already
// the best position. The only bad situations could happen if position is on chunk borders.
this.finishTask();
return;
}
if (!this.chunksToScanIterator.hasNext())
{
// Chunk scanning has completed. Now check positions.
this.finishTask();
return;
}
// Get the chunk
Pair<Integer, Integer> chunkPair = this.chunksToScanIterator.next();
this.chunksToScanIterator.remove();
// Get the chunk snapshot and scan it
Util.getChunkAtAsync(this.world, chunkPair.x, chunkPair.z).
thenApply(Chunk::getChunkSnapshot).
whenCompleteAsync((snapshot, e) ->
{
if (snapshot != null)
{
// Find best spot based on collected information chunks.
this.scanAndPopulateBlockQueue(snapshot);
}
this.checking.set(false);
});
}
/**
* Gets a set of chunk coordinates that will be scanned.
*
* @return - list of chunk coordinates to be scanned
*/
private List<Pair<Integer, Integer>> getChunksToScan()
{
List<Pair<Integer, Integer>> chunksToScan = new ArrayList<>();
int x = this.location.getBlockX();
int z = this.location.getBlockZ();
int range = 20;
// Normalize block coordinates to chunk coordinates and add extra 1 for visiting.
int numberOfChunks = (((x + range) >> 4) - ((x - range) >> 4) + 1) *
(((z + range) >> 4) - ((z - range) >> 4) + 1);
// Ideally it would be if visitor switch from clockwise to counter-clockwise if X % 16 < 8 and
// up to down if Z % 16 < 8.
int offsetX = 0;
int offsetZ = 0;
for (int i = 0; i < numberOfChunks; ++i)
{
int locationX = x + (offsetX << 4);
int locationZ = z + (offsetZ << 4);
this.addChunk(chunksToScan, new Pair<>(locationX, locationZ), new Pair<>(locationX >> 4, locationZ >> 4));
if (Math.abs(offsetX) <= Math.abs(offsetZ) && (offsetX != offsetZ || offsetX >= 0))
{
offsetX += ((offsetZ >= 0) ? 1 : -1);
}
else
{
offsetZ += ((offsetX >= 0) ? -1 : 1);
}
}
return chunksToScan;
}
/**
* This method adds chunk coordinates to the given chunksToScan list.
* The limitation is that if location is in island, then block coordinate must also be in island space.
* @param chunksToScan List of chunks that will be scanned.
* @param blockCoord Block coordinates that must be in island.
* @param chunkCoord Chunk coordinate.
*/
private void addChunk(List<Pair<Integer, Integer>> chunksToScan,
Pair<Integer, Integer> blockCoord,
Pair<Integer, Integer> chunkCoord)
{
if (!chunksToScan.contains(chunkCoord) &&
this.plugin.getIslandsManager().getIslandAt(this.location).
map(is -> is.inIslandSpace(blockCoord)).orElse(true))
{
chunksToScan.add(chunkCoord);
}
}
/**
* This method populates block queue with all blocks that player can be teleported to.
* Add only positions that are inside BoundingBox and is safe for teleportation.
* @param chunkSnapshot Spigot Chunk Snapshot with blocks.
*/
private void scanAndPopulateBlockQueue(ChunkSnapshot chunkSnapshot)
{
int startY = this.location.getBlockY();
int minY = this.world.getMinHeight();
int maxY = this.world.getMaxHeight();
Vector blockVector = new Vector(this.location.getBlockX(), this.location.getBlockY(), this.location.getBlockZ());
int chunkX = chunkSnapshot.getX() << 4;
int chunkZ = chunkSnapshot.getZ() << 4;
for (int x = 0; x < 16; x++)
{
for (int z = 0; z < 16; z++)
{
for (int y = Math.max(minY, startY - this.range); y < Math.min(maxY, startY + this.range); y++)
{
Vector positionVector = new Vector(chunkX + x, y, chunkZ + z);
if (this.boundingBox.contains(positionVector))
{
// Process positions that are inside bounding box of search area.
PositionData positionData = new PositionData(
positionVector,
chunkSnapshot.getBlockType(x, y - 1, z),
y < maxY ? chunkSnapshot.getBlockType(x, y, z) : null,
y + 1 < maxY ? chunkSnapshot.getBlockType(x, y + 1, z) : null,
blockVector.distanceSquared(positionVector));
if (this.plugin.getIslandsManager().checkIfSafe(this.world,
positionData.block,
positionData.spaceOne,
positionData.spaceTwo))
{
// Add only safe locations to the queue.
this.blockQueue.add(positionData);
}
}
}
}
}
}
/**
* This method finishes the chunk loading task and checks from all remaining positions in block queue
* to find the best location for teleportation.
*
* This method stops position finding task and process teleporation.
*/
private void finishTask()
{
// Still Async!
// Nothing left to check and still not canceled
this.task.cancel();
if (this.scanBlockQueue())
{
return;
}
if (this.portal && this.noPortalPosition != null)
{
this.teleportEntity(this.noPortalPosition);
}
else if (this.entity instanceof Player player)
{
// Return to main thread and teleport the player
Bukkit.getScheduler().runTask(this.plugin, () ->
{
// Failed, no safe spot
if (!this.failureMessage.isEmpty())
{
User.getInstance(this.entity).notify(this.failureMessage);
}
// Check highest block
Block highestBlock = this.world.getHighestBlockAt(this.location);
if (highestBlock.getType().isSolid() &&
this.plugin.getIslandsManager().isSafeLocation(highestBlock.getLocation()))
{
// Try to teleport player to the highest block.
this.asyncTeleport(highestBlock.getLocation().add(new Vector(0.5D, 0D, 0.5D)));
return;
}
else if (!this.plugin.getIWM().inWorld(this.entity.getLocation()))
{
// Last resort
player.performCommand("spawn");
}
else if (!this.cancelIfFail)
{
// Create a spot for the player to be
if (this.world.getEnvironment().equals(World.Environment.NETHER))
{
this.makeAndTeleport(Material.NETHERRACK);
}
else if (this.world.getEnvironment().equals(World.Environment.THE_END))
{
this.makeAndTeleport(Material.END_STONE);
}
else
{
this.makeAndTeleport(Material.COBBLESTONE);
}
}
if (this.failRunnable != null)
{
Bukkit.getScheduler().runTask(this.plugin, this.failRunnable);
}
this.result.complete(false);
});
}
else
{
// We do not teleport entities if position failed.
if (this.failRunnable != null)
{
Bukkit.getScheduler().runTask(this.plugin, this.failRunnable);
}
this.result.complete(false);
}
}
/**
* This method creates a spot in start location for player to be teleported to. It creates 2 base material blocks
* above location and fills the space between them with air.
* @param baseMaterial Material that will be for top and bottom block.
*/
private void makeAndTeleport(Material baseMaterial)
{
this.location.getBlock().getRelative(BlockFace.DOWN).setType(baseMaterial, false);
this.location.getBlock().setType(Material.AIR, false);
this.location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false);
this.location.getBlock().getRelative(BlockFace.UP).getRelative(BlockFace.UP).setType(baseMaterial, false);
// Teleport player to the location of the empty space.
this.asyncTeleport(this.location.clone().add(new Vector(0.5D, 0D, 0.5D)));
}
/**
* This method scans all populated positions and returns true if position is found, or false, if not.
* @return {@code true} if safe position is found, otherwise false.
*/
private boolean scanBlockQueue()
{
boolean blockFound = false;
while (!this.blockQueue.isEmpty() && !blockFound)
{
blockFound = this.checkPosition(this.blockQueue.poll());
}
return blockFound;
}
/**
* This method triggers a task that will teleport entity in a main thread.
*/
private void teleportEntity(final Location location)
{
// Return to main thread and teleport the player
Bukkit.getScheduler().runTask(this.plugin, () -> this.asyncTeleport(location));
}
/**
* This method performs async teleportation and runs end tasks for spot-finder.
* @param location Location where player should be teleported.
*/
private void asyncTeleport(final Location location)
{
Util.teleportAsync(this.entity, location).thenRun(() ->
{
if (this.successRunnable != null)
{
Bukkit.getScheduler().runTask(this.plugin, this.successRunnable);
}
this.result.complete(true);
});
}
/**
* This method checks if given position is valid for teleportation.
* If query should find portal, then it marks first best position as noPortalPosition and continues
* to search for a valid portal.
* If query is not in portal mode, then return first valid position.
* @param positionData Position data that must be checked.
* @return {@code true} if position is found and no extra processing required, {@code false} otherwise.
*/
private boolean checkPosition(PositionData positionData)
{
if (this.portal)
{
if (Material.NETHER_PORTAL.equals(positionData.spaceOne()) ||
Material.NETHER_PORTAL.equals(positionData.spaceTwo()))
{
// Portal is found. Teleport entity to the portal location.
this.teleportEntity(new Location(this.world,
positionData.vector().getBlockX() + 0.5,
positionData.vector().getBlockY() + 0.1,
positionData.vector().getBlockZ() + 0.5,
this.location.getYaw(),
this.location.getPitch()));
// Position found and player can is already teleported to it.
return true;
}
else if (this.noPortalPosition == null)
{
// Mark first incoming position as the best for teleportation.
this.noPortalPosition = new Location(this.world,
positionData.vector().getBlockX() + 0.5,
positionData.vector().getBlockY() + 0.1,
positionData.vector().getBlockZ() + 0.5,
this.location.getYaw(),
this.location.getPitch());
}
}
else
{
// First best position should be valid for teleportation.
this.teleportEntity(new Location(this.world,
positionData.vector().getBlockX() + 0.5,
positionData.vector().getBlockY() + 0.1,
positionData.vector().getBlockZ() + 0.5,
this.location.getYaw(),
this.location.getPitch()));
return true;
}
return false;
}
/**
* PositionData record holds information about position where player will be teleported.
* @param vector Vector of the position.
* @param distance Distance till the position.
* @param block Block on which player will be placed.
* @param spaceOne One block above block.
* @param spaceTwo Two blocks above block.
*/
private record PositionData(Vector vector, Material block, Material spaceOne, Material spaceTwo, double distance) {}
public static Builder builder(BentoBox plugin)
{
return new Builder(plugin);
}
// ---------------------------------------------------------------------
// Section: Builder
// ---------------------------------------------------------------------
public static class Builder
{
private Builder(BentoBox plugin)
{
this.plugin = plugin;
this.result = new CompletableFuture<>();
}
// ---------------------------------------------------------------------
// Section: Builders
// ---------------------------------------------------------------------
/**
* Set who or what is going to teleport
*
* @param entity entity to teleport
* @return Builder
*/
public Builder entity(Entity entity)
{
this.entity = entity;
return this;
}
/**
* Set the desired location
*
* @param location the location
* @return Builder
*/
public Builder location(Location location)
{
this.location = location;
return this;
}
/**
* This is a portal teleportation
*
* @return Builder
*/
public Builder portal()
{
this.portal = true;
return this;
}
/**
* This is a successRunnable for teleportation
*
* @return Builder
*/
public Builder successRunnable(Runnable successRunnable)
{
this.successRunnable = successRunnable;
return this;
}
/**
* Try to teleport the player
*
* @return ClosestSafeSpotTeleport
*/
@Nullable
public ClosestSafeSpotTeleport build()
{
// Error checking
if (this.entity == null)
{
this.plugin.logError("Attempt to safe teleport a null entity!");
this.result.complete(null);
return null;
}
if (this.location == null)
{
this.plugin.logError("Attempt to safe teleport to a null location!");
this.result.complete(null);
return null;
}
if (this.location.getWorld() == null)
{
this.plugin.logError("Attempt to safe teleport to a null world!");
this.result.complete(null);
return null;
}
if (this.failureMessage.isEmpty() && this.entity instanceof Player)
{
this.failureMessage = "general.errors.no-safe-location-found";
}
return new ClosestSafeSpotTeleport(this);
}
// ---------------------------------------------------------------------
// Section: Getters
// ---------------------------------------------------------------------
/**
* Gets plugin.
*
* @return the plugin
*/
public BentoBox getPlugin()
{
return this.plugin;
}
/**
* Gets result.
*
* @return the result
*/
public CompletableFuture<Boolean> getResult()
{
return this.result;
}
/**
* Gets entity.
*
* @return the entity
*/
public Entity getEntity()
{
return this.entity;
}
/**
* Gets location.
*
* @return the location
*/
public Location getLocation()
{
return this.location;
}
/**
* Gets world.
*
* @return the world
*/
public World getWorld()
{
return this.world;
}
/**
* Gets success runnable.
*
* @return the success runnable
*/
public Runnable getSuccessRunnable()
{
return this.successRunnable;
}
/**
* Gets fail runnable.
*
* @return the fail runnable
*/
public Runnable getFailRunnable()
{
return this.failRunnable;
}
/**
* Gets failure message.
*
* @return the failure message
*/
public String getFailureMessage()
{
return this.failureMessage;
}
/**
* Is portal boolean.
*
* @return the boolean
*/
public boolean isPortal()
{
return this.portal;
}
/**
* Is cancel if fail boolean.
*
* @return the boolean
*/
public boolean isCancelIfFail()
{
return this.cancelIfFail;
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* BentoBox plugin instance.
*/
private final BentoBox plugin;
/**
* CompletableFuture that is triggered upon finishing position searching.
*/
private final CompletableFuture<Boolean> result;
/**
* Entity that will be teleported.
*/
private Entity entity;
/**
* Start location of teleportation.
*/
private Location location;
/**
* World where teleportation happens.
*/
private World world;
/**
* Runnable that will be triggered after successful teleportation.
*/
private Runnable successRunnable;
/**
* Runnable that will be triggered after failing teleportation.
*/
private Runnable failRunnable;
/**
* Stores the failure message that is sent to a player.
*/
private String failureMessage = "";
/**
* Boolean that indicates if teleportation should search for portal.
*/
private boolean portal;
/**
* Boolean that indicates if failing teleport should cancel it or create spot for player.
*/
private boolean cancelIfFail;
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
/**
* This comparator sorts position data based in order:
* - the smallest distance value
* - the smallest x value
* - the smallest z value
* - the smallest y value
*/
private final static Comparator<PositionData> POSITION_COMPARATOR = Comparator.comparingDouble(PositionData::distance).
thenComparingInt(position -> position.vector().getBlockX()).
thenComparingInt(position -> position.vector().getBlockZ()).
thenComparingInt(position -> position.vector().getBlockY());
/**
* Stores chunk load speed.
*/
private static final long CHUNK_LOAD_SPEED = 1;
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* BentoBox plugin instance.
*/
private final BentoBox plugin;
/**
* Entity that will be teleported.
*/
private final Entity entity;
/**
* Start location of teleportation.
*/
private final Location location;
/**
* World where teleportation happens.
*/
private final World world;
/**
* Runnable that will be triggered after successful teleportation.
*/
private final Runnable successRunnable;
/**
* Runnable that will be triggered after failing teleportation.
*/
private final Runnable failRunnable;
/**
* Stores the failure message that is sent to a player.
*/
private final String failureMessage;
/**
* CompletableFuture that is triggered upon finishing position searching.
*/
private final CompletableFuture<Boolean> result;
/**
* Boolean that indicates if teleportation should search for portal.
*/
private final boolean portal;
/**
* Boolean that indicates if failing teleport should cancel it or create spot for player.
*/
private final boolean cancelIfFail;
/**
* Local variable that indicates if current process is running.
*/
private final AtomicBoolean checking = new AtomicBoolean();
/**
* The distance from starting location in all directions where new position will be searched.
*/
private int range;
/**
* Block Queue for all blocks that should be validated.
*/
private Queue<PositionData> blockQueue;
/**
* List of chunks that will be scanned for positions.
*/
private Iterator<Pair<Integer, Integer>> chunksToScanIterator;
/**
* BoundingBox where teleportation can happen. Areas outside are illegal.
*/
private BoundingBox boundingBox;
/**
* This method returns first best available spot if portal was not found in search area.
*/
private Location noPortalPosition;
/**
* Bukkit task that processes chunks.
*/
private BukkitTask task;
}

View File

@ -176,28 +176,40 @@ public class ServerCompatibility {
/**
* @since 1.16.0
*/
V1_16_5(Compatibility.NOT_SUPPORTED),
V1_16_5(Compatibility.INCOMPATIBLE),
/**
* @since 1.17.0
*/
V1_17(Compatibility.NOT_SUPPORTED),
V1_17(Compatibility.INCOMPATIBLE),
/**
* @since 1.17.1
*/
V1_17_1(Compatibility.SUPPORTED),
V1_17_1(Compatibility.INCOMPATIBLE),
/**
* @since 1.19.0
*/
V1_18(Compatibility.COMPATIBLE),
V1_18(Compatibility.SUPPORTED),
/**
* @since 1.19.0
*/
V1_18_1(Compatibility.COMPATIBLE),
V1_18_1(Compatibility.SUPPORTED),
/**
* @since 1.20.1
*/
V1_18_2(Compatibility.COMPATIBLE),
V1_18_2(Compatibility.SUPPORTED),
/**
* @since 1.21.0
*/
V1_19(Compatibility.COMPATIBLE),
/**
* @since 1.21.0
*/
V1_19_1(Compatibility.COMPATIBLE),
/**
* @since 1.21.0
*/
V1_19_2(Compatibility.COMPATIBLE),
;
private final Compatibility compatibility;

View File

@ -209,6 +209,11 @@ island:
# If set to 0 or lower, the plugin will not expand the y-coordinate.
# Added since 1.19.1.
safe-spot-search-vertical-range: 400
# By default, if the destination is not safe, the plugin will try to search for a safe spot around the destination.
# This allows to change the distance for searching this spot. Larger value will mean longer position search.
# This value is also used for valid nether portal linking between dimension.
# Added since 1.21.0.
safe-spot-search-range: 16
web:
github:
# Toggle whether BentoBox can connect to GitHub to get data about updates and addons.

View File

@ -340,6 +340,7 @@ commands:
prompt: Napiš jméno, nebo 'quit' ke zrušení
too-long: '&c Příliš dlouhé'
pick-a-unique-name: Prosím, zvol více jedinečný název
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Povedlo se!
conversation-prefix: '>'
description:

View File

@ -391,6 +391,7 @@ commands:
prompt: Gib einen Namen ein, oder 'quit' zum Beenden
too-long: "&c Zu lang"
pick-a-unique-name: Wähle bitte einen eindeutigeren Namen
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Erfolg!
conversation-prefix: ">"
description:

View File

@ -102,6 +102,7 @@ commands:
status: "&b [purged] &a islands purged out of &b [purgeable] &7(&b[percentage] %&7)&a."
team:
description: "manage teams"
add:
parameters: "<owner> <player>"
description: "add player to owner's team"
@ -331,7 +332,7 @@ commands:
rename:
parameters: "<blueprint name> <new name>"
description: "rename a blueprint"
success: "&a Blueprint &b [old] &a has been successfully renamed to &b [name]&a."
success: "&a Blueprint &b [old] &a has been successfully renamed to &b [display]&a. Filename now is &b [name]&a."
pick-different-name: "&c Please specify a name that is different from the blueprint's current name."
management:
back: "Back"
@ -365,8 +366,9 @@ commands:
name:
quit: "quit"
prompt: "Enter a name, or 'quit' to quit"
too-long: "&c Too long"
too-long: "&c Too long name. Only 32 chars are allowed."
pick-a-unique-name: "Please pick a more unique name"
stripped-char-in-unique-name: "&c Some chars were removed because they are not allowed. &a New ID will be &b [name]&a."
success: "Success!"
conversation-prefix: ">"
description:
@ -421,6 +423,9 @@ commands:
description: "removes deaths to the player"
parameters: "<player> <deaths>"
success: "&a Successfully removed &b [number] &a deaths to &b [name], decreasing the total to &b [total]&a deaths."
resetname:
description: "reset player island name"
success: "&a Successfully reset [name]'s island name."
bentobox:
description: "BentoBox admin command"
about:
@ -496,6 +501,7 @@ commands:
estimated-time: "&a Estimated time: &b [number] &a seconds."
blocks: "&a Building it block by block: &b [number] &a blocks in all..."
entities: "&a Filling it with entities: &b [number] &a entities in all..."
dimension-done: "&a Island in [world] is constructed."
done: "&a Done! Your island is ready and waiting for you!"
pick: "&2 Pick an island"
unknown-blueprint: "&c That blueprint has not been loaded yet."
@ -731,6 +737,10 @@ ranks:
protection:
command-is-banned: "Command is banned for visitors"
flags:
ALLAY:
name: "Allay interaction"
description: "Allow giving and taking items to/from Allay"
hint: "Allay interaction disabled"
ANIMAL_NATURAL_SPAWN:
description: "Toggle natural animal spawning"
name: "Animal natural spawn"
@ -745,6 +755,10 @@ protection:
description: "Toggle interaction"
name: "Armor stands"
hint: "Armor stand use disabled"
AXOLOTL_SCOOPING:
name: "Axolotl Scooping"
description: "Allow scooping of axolotl using a bucket"
hint: "Axolotl scooping disabled"
BEACON:
description: "Toggle interaction"
name: "Beacons"
@ -889,6 +903,12 @@ protection:
&a (override Buckets)
name: "Collect water"
hint: "Water buckets disabled"
COLLECT_POWDERED_SNOW:
description: |-
&a Toggle collecting powdered snow
&a (override Buckets)
name: "Collect powdered snow"
hint: "Powdered snow buckets disabled"
COMMAND_RANKS:
name: "&e Command Ranks"
description: "&a Configure command ranks"
@ -1271,6 +1291,18 @@ protection:
&a using spawn eggs.
name: "Spawn eggs on spawners"
hint: "changing a spawner's entity type using spawn eggs is not allowed"
SCULK_SENSOR:
description: |-
&a Toggles sculk sensor
&a activation.
name: "Sculk Sensor"
hint: "sculk sensor activation is disabled"
SCULK_SHRIEKER:
description: |-
&a Toggles sculk shrieker
&a activation.
name: "Sculk Shrieker"
hint: "sculk shrieker activation is disabled"
TNT_DAMAGE:
description: |-
&a Allow TNT and TNT minecarts
@ -1330,6 +1362,20 @@ protection:
&a
&a Island members still lose their items
&a if they die on their own island!
VISITOR_TRIGGER_RAID:
name: "Visitors triggers raids"
description: |-
&a Toggles if visitors can start
&a a raid on an island which they are
&a visiting.
&a
&a Bad Omen effect will be removed!
ENTITY_PORTAL_TELEPORT:
name: "Entity portal usage"
description: |-
&a Toggles if entities (non-player) can
&a use portals to teleport between
&a dimensions
WITHER_DAMAGE:
name: "Toggle wither damage"
description: |-

View File

@ -362,6 +362,7 @@ commands:
prompt: Ingrese un nombre o 'quit' para salir
too-long: "&cDemasiado largo"
pick-a-unique-name: Elige un nombre más exclusivo
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: "¡Éxito!"
conversation-prefix: ">"
description:

View File

@ -86,6 +86,7 @@ commands:
name:
conversation-prefix: ">"
pick-a-unique-name: Veuillez choisir un nom plus unique
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
prompt: Entrez un nom, ou "quitter" pour quitter
quit: quitter
success: Succès !

View File

@ -78,6 +78,7 @@ commands:
name:
conversation-prefix: ">"
pick-a-unique-name: Scegli un nome unico
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
prompt: Inserisci un nome, o 'quit' per uscire
success: Successo!
too-long: "&cTroppo lungo"

View File

@ -328,6 +328,7 @@ commands:
prompt: 名前を入力するか、「quit」で終了します
too-long: "&c長すぎる"
pick-a-unique-name: よりユニークな名前を選んでください
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: 成功!
conversation-prefix: ">"
description:

View File

@ -337,6 +337,7 @@ commands:
prompt: 이름을 입력하세요, quit를 입력하여 종료할수 있습니다
too-long: "&c 너무 깁니다"
pick-a-unique-name: 더 독특한 이름을 선택하십시오
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: 완료!
conversation-prefix: ">"
description:

View File

@ -90,6 +90,7 @@ commands:
name:
conversation-prefix: ">"
pick-a-unique-name: Lūdzu izvēlies unikālu nosaukumu
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
prompt: Ieraksti vārdu vai 'iziet', lai izietu
quit: iziet
success: Izdevās!

View File

@ -393,6 +393,7 @@ commands:
prompt: Voer een naam in of 'quit' om te stoppen
too-long: "&c Te lang"
pick-a-unique-name: Kies een meer unieke naam
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Succes!
conversation-prefix: ">"
description:

View File

@ -345,6 +345,7 @@ commands:
prompt: Wprowadź nazwę, lub wpisz 'wyjdź', by wyjść
too-long: '&cNazwa zbyt długa'
pick-a-unique-name: Wybierz bardziej unikalną nazwę
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Sukces!
conversation-prefix: '>'
description:

View File

@ -354,6 +354,7 @@ commands:
prompt: Digite um nome, ou 'quit' para sair
too-long: '&c Muito comprido'
pick-a-unique-name: Por favor escolha um nome único
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Sucesso!
conversation-prefix: '>'
description:

View File

@ -373,6 +373,7 @@ commands:
prompt: Introduceți un nume sau „renunțați” pentru a renunța
too-long: "&c Prea mult"
pick-a-unique-name: Vă rugăm să alegeți un nume mai unic
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Succes!
conversation-prefix: ">"
description:

View File

@ -384,6 +384,7 @@ commands:
prompt: İsim gir ya da çıkmak için 'quit' yaz.
too-long: "&cÇok uzun."
pick-a-unique-name: Lütfen daha benzersiz bir ad seçin
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Başarılı!
conversation-prefix: ">"
description:

View File

@ -376,6 +376,7 @@ commands:
prompt: Enter a name, or 'quit' to quit
too-long: "&c Too long"
pick-a-unique-name: Please pick a more unique name
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: Success!
conversation-prefix: ">"
description:

View File

@ -367,6 +367,7 @@ commands:
prompt: "&e请输入新名称 或 “&b quit&e” 来退出编辑。"
too-long: "&c新名称太长了"
pick-a-unique-name: "&c这个名称已存在 请另选一个不同的名称!"
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
success: "&a成功"
conversation-prefix: "&3> &r"
description:

View File

@ -1,7 +1,7 @@
name: BentoBox
main: world.bentobox.bentobox.BentoBox
version: ${project.version}${build.number}
api-version: "1.17"
api-version: "1.18"
authors: [tastybento, Poslovitch]
contributors: ["The BentoBoxWorld Community"]
@ -17,7 +17,6 @@ softdepend:
- Vault
- PlaceholderAPI
- dynmap
- WorldEdit
- WorldBorderAPI
- BsbMongo
- WorldGeneratorApi
@ -28,6 +27,11 @@ softdepend:
- HolographicDisplays
- EconomyPlus
libraries:
- mysql:mysql-connector-java:8.0.27
- org.mongodb:mongodb-driver:${mongodb.version}
- postgresql:postgresql:9.1-901-1.jdbc4
permissions:
bentobox.admin:
description: Allows admin command usage

View File

@ -24,6 +24,7 @@ import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.After;
import org.junit.Before;
@ -88,9 +89,11 @@ public class IslandTest {
when(iwm.getIslandDistance(any())).thenReturn(DISTANCE);
// Location
//when(location.getWorld()).thenReturn(world);
when(location.clone()).thenReturn(location);
when(world.getName()).thenReturn("bskyblock_world");
when(location.getWorld()).thenReturn(world);
when(world.getEnvironment()).thenReturn(Environment.NORMAL);
when(world.toString()).thenReturn(null);
// User
when(user.getUniqueId()).thenReturn(uuid);
@ -419,7 +422,7 @@ public class IslandTest {
*/
@Test
public void testGetWorld() {
assertNull(i.getWorld());
assertEquals(i.getWorld(), world);
}
/**
@ -564,6 +567,7 @@ public class IslandTest {
public void testGetProtectionBoundingBox() {
i.setWorld(world);
assertNotNull(i.getProtectionBoundingBox());
assertEquals("BoundingBox [minX=-100.0, minY=0.0, minZ=-100.0, maxX=100.0, maxY=0.0, maxZ=100.0]", i.getProtectionBoundingBox().toString());
}
/**
@ -1018,7 +1022,7 @@ public class IslandTest {
*/
@Test
public void testSetCooldowns() {
i.setCooldowns(Collections.singletonMap(Flags.BREAK_BLOCKS, 123L));
i.setCooldowns(Collections.singletonMap(Flags.BREAK_BLOCKS.getID(), 123L));
assertFalse(i.getCooldowns().isEmpty());
}

View File

@ -110,13 +110,13 @@ public class GeoMobLimitTabTest {
assertEquals("AXOLOTL", list.get(0));
// Click on AXOLOTL
tab.onClick(panel, user, ClickType.LEFT, 9);
tab.onClick(panel, user, ClickType.LEFT, 10);
list.forEach(System.out::println);
assertEquals(2, list.size());
assertEquals("COW", list.get(1));
assertEquals("BAT", list.get(0));
// Click on AXOLOTL again to have it added
tab.onClick(panel, user, ClickType.LEFT, 9);
tab.onClick(panel, user, ClickType.LEFT, 10);
assertEquals(3, list.size());
assertEquals("BAT", list.get(0));
assertEquals("COW", list.get(1));

View File

@ -69,7 +69,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup {
clickedBlocks.put(Material.WHITE_BED, Flags.BED);
when(Tag.BEDS.isTagged(Material.WHITE_BED)).thenReturn(true);
clickedBlocks.put(Material.BREWING_STAND, Flags.BREWING);
clickedBlocks.put(Material.CAULDRON, Flags.BREWING);
clickedBlocks.put(Material.WATER_CAULDRON, Flags.COLLECT_WATER);
clickedBlocks.put(Material.BARREL, Flags.BARREL);
clickedBlocks.put(Material.CHEST, Flags.CHEST);
clickedBlocks.put(Material.CHEST_MINECART, Flags.CHEST);
@ -87,6 +87,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup {
clickedBlocks.put(Material.IRON_TRAPDOOR, Flags.TRAPDOOR);
when(Tag.TRAPDOORS.isTagged(Material.IRON_TRAPDOOR)).thenReturn(true);
clickedBlocks.put(Material.SPRUCE_FENCE_GATE, Flags.GATE);
when(Tag.FENCE_GATES.isTagged(Material.SPRUCE_FENCE_GATE)).thenReturn(true);
clickedBlocks.put(Material.BLAST_FURNACE, Flags.FURNACE);
clickedBlocks.put(Material.CAMPFIRE, Flags.FURNACE);
clickedBlocks.put(Material.FURNACE_MINECART, Flags.FURNACE);
@ -110,7 +111,9 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup {
clickedBlocks.put(Material.DRAGON_EGG, Flags.DRAGON_EGG);
clickedBlocks.put(Material.END_PORTAL_FRAME, Flags.PLACE_BLOCKS);
clickedBlocks.put(Material.ITEM_FRAME, Flags.ITEM_FRAME);
clickedBlocks.put(Material.GLOW_ITEM_FRAME, Flags.ITEM_FRAME);
clickedBlocks.put(Material.SWEET_BERRY_BUSH, Flags.BREAK_BLOCKS);
clickedBlocks.put(Material.CAVE_VINES, Flags.BREAK_BLOCKS);
clickedBlocks.put(Material.CAKE, Flags.CAKE);
clickedBlocks.put(Material.BEEHIVE, Flags.HIVE);
clickedBlocks.put(Material.BEE_NEST, Flags.HIVE);
@ -134,6 +137,8 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup {
// Nothing in hand right now
when(item.getType()).thenReturn(Material.AIR);
when(player.getInventory()).thenReturn(inv);
when(inv.getItemInMainHand()).thenReturn(item);
when(inv.getItemInOffHand()).thenReturn(new ItemStack(Material.BUCKET));
// FlagsManager
setFlags();

View File

@ -45,6 +45,8 @@ import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.util.Util;
import world.bentobox.bentobox.versions.ServerCompatibility;
@RunWith(PowerMockRunner.class)
@PrepareForTest( {BentoBox.class, Bukkit.class, Flags.class, Util.class })
@ -78,6 +80,10 @@ public class MobSpawnListenerTest {
when(server.getWorld("world")).thenReturn(world);
when(server.getVersion()).thenReturn("BSB_Mocking");
ServerCompatibility serverCompatibility = mock(ServerCompatibility.class);
Whitebox.setInternalState(ServerCompatibility.class, "instance", serverCompatibility);
when(serverCompatibility.getServerVersion()).thenReturn(ServerCompatibility.ServerVersion.V1_19);
PluginManager pim = mock(PluginManager.class);
ItemFactory itemFactory = mock(ItemFactory.class);

View File

@ -19,11 +19,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.Cow;
import org.bukkit.entity.Entity;
@ -50,6 +46,7 @@ import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
@ -64,18 +61,25 @@ import world.bentobox.bentobox.util.Util;
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest( {Bukkit.class, BentoBox.class, Flags.class, Util.class} )
public class ChestDamageListenerTest {
public class ChestDamageListenerTest extends AbstractCommonSetup
{
private Location location;
private BentoBox plugin;
private World world;
@Override
@Before
public void setUp() {
public void setUp() throws Exception {
super.setUp();
// Set up plugin
plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Tags
when(Tag.SHULKER_BOXES.isTagged(any(Material.class))).thenReturn(false);
Server server = mock(Server.class);
world = mock(World.class);
when(server.getLogger()).thenReturn(Logger.getAnonymousLogger());
@ -161,8 +165,6 @@ public class ChestDamageListenerTest {
// Util
PowerMockito.mockStatic(Util.class);
when(Util.getWorld(Mockito.any())).thenReturn(mock(World.class));
}
@After

View File

@ -39,6 +39,8 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -345,7 +347,7 @@ public class BlueprintClipboardManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -361,14 +363,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "test1234"));
assertTrue(bcm.save(user, "test1234", ""));
File bp = new File(blueprintFolder, "test1234.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -384,14 +386,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "test.1234/../../film"));
File bp = new File(blueprintFolder, "test1234film.blu");
assertTrue(bcm.save(user, Util.sanitizeInput("test.1234/../../film"), ""));
File bp = new File(blueprintFolder, "test.1234_.._.._film.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -407,14 +409,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "日本語の言葉"));
assertTrue(bcm.save(user, "日本語の言葉", ""));
File bp = new File(blueprintFolder, "日本語の言葉.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -430,8 +432,9 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "日本語の言葉/../../../config"));
File bp = new File(blueprintFolder, "日本語の言葉config.blu");
assertTrue(bcm.save(user, Util.sanitizeInput("日本語の言葉/../../../config"), ""));
File bp = new File(blueprintFolder, "日本語の言葉_.._.._.._config.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}

View File

@ -526,8 +526,6 @@ public class BlueprintsManagerTest {
BlueprintsManager bpm = new BlueprintsManager(plugin);
bpm.addBlueprintBundle(addon, bb);
assertEquals("bundle", bpm.validate(addon, "bundle"));
// Mixed case
assertEquals("buNdle", bpm.validate(addon, "buNdle"));
// Not there
assertNull(bpm.validate(addon, "buNdle2"));
}
@ -651,18 +649,19 @@ public class BlueprintsManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String, java.lang.String)}.
*/
@Test
public void testRenameBlueprint() {
// Save it
BlueprintsManager bpm = new BlueprintsManager(plugin);
bpm.saveBlueprint(addon, defaultBp);
bpm.addBlueprint(addon, defaultBp);
File blueprints = new File(dataFolder, BlueprintsManager.FOLDER_NAME);
File d = new File(blueprints, "bedrock.blu");
assertTrue(d.exists());
// Rename it
bpm.renameBlueprint(addon, defaultBp, "bedrock2");
bpm.renameBlueprint(addon, defaultBp, "bedrock2", "");
assertFalse(d.exists());
d = new File(blueprints, "bedrock2.blu");
assertTrue(d.exists());

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