mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-11-23 11:15:24 +01:00
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:
commit
1a59ca7785
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -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
34
pom.xml
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (clipboards.containsKey(user.getUniqueId())) {
|
||||
BlueprintClipboard clipboard = clipboards.get(user.getUniqueId());
|
||||
paintAxis(user, clipboard);
|
||||
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;
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
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()));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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())) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
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()
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
/**
|
||||
|
@ -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());
|
||||
|
||||
if (copyBiome) {
|
||||
// Biome
|
||||
b.setBiome(block.getBiome());
|
||||
}
|
||||
|
||||
// Signs
|
||||
if (blockState instanceof Sign sign) {
|
||||
b.setSignLines(Arrays.asList(sign.getLines()));
|
||||
|
@ -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,9 +134,8 @@ 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()));
|
||||
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,
|
||||
@ -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());
|
||||
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++;
|
||||
}
|
||||
while (pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && bits.it2.hasNext()) {
|
||||
pasteBlock(location, bits.it2.next());
|
||||
count++;
|
||||
if (!blockMap.isEmpty()) {
|
||||
currentTask = paster.pasteBlocks(island, world, blockMap);
|
||||
}
|
||||
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()) {
|
||||
} else {
|
||||
if (pasteState.equals(PasteState.BLOCKS)) {
|
||||
// Blocks done
|
||||
// Next paste attachments
|
||||
pasteState = PasteState.ATTACHMENTS;
|
||||
}
|
||||
else if (pasteState.equals(PasteState.ATTACHMENTS) && !bits.it2.hasNext()) {
|
||||
} 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()) {
|
||||
}
|
||||
}
|
||||
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;
|
||||
owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.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
|
||||
@ -238,135 +256,6 @@ public class BlueprintPaster {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the minimum and maximum block positions
|
||||
* @param l - location of block pasted
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
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) {
|
||||
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 {
|
||||
|
||||
context.setSessionData("uniqueId", uniqueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default stays as default
|
||||
context.setSessionData("uniqueId", bb.getUniqueId());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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,9 +294,11 @@ 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++) ;
|
||||
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
else if (e.getItem().getType() == Material.ENDER_PEARL)
|
||||
{
|
||||
this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.ENDER_PEARL);
|
||||
}
|
||||
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.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())) {
|
||||
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 -> checkIsland(e, player, loc, flag));
|
||||
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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
// 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));
|
||||
}
|
||||
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
|
||||
}
|
||||
// Helper
|
||||
// if (Enums.getIfPresent(EntityType.class, "<name>").isPresent()) {
|
||||
// bi.put(EntityType.<type>, Collections.singletonList(Material.<material>));
|
||||
// }
|
||||
BREEDING_ITEMS = Collections.unmodifiableMap(bi);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
if (e.getRightClicked() instanceof Vehicle)
|
||||
{
|
||||
if (e.getRightClicked() instanceof Animals)
|
||||
{
|
||||
// Animal riding
|
||||
if (e.getRightClicked() instanceof Animals) {
|
||||
checkIsland(e, p, l, Flags.RIDING);
|
||||
this.checkIsland(e, p, l, Flags.RIDING);
|
||||
}
|
||||
else if (e.getRightClicked() instanceof RideableMinecart)
|
||||
{
|
||||
// Minecart riding
|
||||
else if (e.getRightClicked() instanceof RideableMinecart) {
|
||||
checkIsland(e, p, l, Flags.MINECART);
|
||||
this.checkIsland(e, p, l, Flags.MINECART);
|
||||
}
|
||||
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
|
||||
else if (e.getRightClicked() instanceof Boat) {
|
||||
checkIsland(e, p, l, Flags.BOAT);
|
||||
this.checkIsland(e, p, l, Flags.BOAT);
|
||||
}
|
||||
}
|
||||
else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader)
|
||||
{
|
||||
// Villager trading
|
||||
else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
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
|
||||
else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) {
|
||||
checkIsland(e, p, l, Flags.NAME_TAG);
|
||||
this.checkIsland(e, p, l, Flags.NAME_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
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;
|
||||
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);
|
||||
}).
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)) {
|
||||
return;
|
||||
}
|
||||
if (p.getShooter() instanceof Player) {
|
||||
if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType())) {
|
||||
checkIsland(e, (Player)p.getShooter(), e.getBlock().getLocation(), Flags.BUTTON);
|
||||
public void onProjectileHit(EntityInteractEvent e)
|
||||
{
|
||||
if (!(e.getEntity() instanceof Projectile p))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPressurePlate(e.getBlock().getType())) {
|
||||
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 (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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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:
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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")) {
|
||||
checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.BOAT);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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()) {
|
||||
|
||||
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);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
// Spawners
|
||||
case SPAWNER:
|
||||
boolean cancelSpawners = shouldCancel(e.getEntity(), e.getLocation(), Flags.ANIMAL_SPAWNERS_SPAWN, Flags.MONSTER_SPAWNERS_SPAWN);
|
||||
case SPAWNER ->
|
||||
{
|
||||
boolean cancelSpawners = this.shouldCancel(e.getEntity(),
|
||||
e.getLocation(),
|
||||
Flags.ANIMAL_SPAWNERS_SPAWN,
|
||||
Flags.MONSTER_SPAWNERS_SPAWN);
|
||||
e.setCancelled(cancelSpawners);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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?");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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)
|
||||
*/
|
||||
@ -571,6 +563,20 @@ public final class Flags {
|
||||
*/
|
||||
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.
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
||||
File bpf = this.getBlueprintsFolder(addon);
|
||||
// Get the filename
|
||||
File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX);
|
||||
File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX);
|
||||
// Delete the old file
|
||||
try {
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
36
src/main/java/world/bentobox/bentobox/nms/PasteHandler.java
Normal file
36
src/main/java/world/bentobox/bentobox/nms/PasteHandler.java
Normal 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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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]))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
|
238
src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
Normal file
238
src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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)) {
|
||||
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();
|
||||
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++;
|
||||
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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,9 +341,20 @@ public class Util {
|
||||
// Bat extends Mob
|
||||
// Most of passive mobs extends Animals
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PaperLib methods for addons to call
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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: |-
|
||||
|
@ -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:
|
||||
|
@ -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 !
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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!
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user